# 第14週- IoT WiFi ###### tags: `WiFi` `IoT` `遠距控制` 了解各類 IoT 無線網路、網路架構基礎知識,並練習使用 ESP32 WiFi 連網功能,下載資料、上傳控制,或充當網路伺服器管理資源。 --- ## IoT 網路遠距控制的方式 ### IoT 無線網路一覽表: ![](https://hackmd.io/_uploads/HkNQ8oTSn.png) **1) LPWAN (NB-IoT, LTE-M)** * ESP32 需加裝專用發射接收模組,插 SIM 卡 * 直接接入現有的 LTE 網路,重覆利用現有移動通訊基地台和設備 * 電信付費頻段,可深度休眠 * 傳輸頻寬,NB-IoT:~100kbps;LTE-M:~1Mbps * 傳輸距離:10KM 以上, 唯 LTE-M 可漫遊 * 最貴 * 需額外月租費 ![](https://hackmd.io/_uploads/rk-2EopHh.png) **2) LoRa** * ESP32 需加裝專用發射接收模組 * 需要設置節點、路由器 * ISM頻段 433/868/915MHz * 傳輸頻寬為 0.3K~50kbps * 傳輸距離:依電波穿透率 2KM ~ 15KM 以上 * 比較便宜 ![](https://hackmd.io/_uploads/HJKVdfCS3.png) **3) WiFi網路** * ESP32 已內含WiFi硬體,並具有天線 * 傳輸頻寬為 1-11Mbps(2.4GHz)/1-500Mbps(5GHz) * 工作功率較高,使用 ISM 頻段中的 2.4G/5GHz 雙頻 * 價位界於中間 * 可以當成是工作站或熱點、或兩者同時,構成 IoT 普通採用的行動隨意網路 (MANET, Mobile ad‐hoc network) 或無線隨意網路 (WANET, Wireless ad‐hoc network) * 可以證書方式認證並加密 ![](https://hackmd.io/_uploads/BkCGAY2r2.png) --- ## WiFi.h 函式庫有四種無線模式: ![](https://hackmd.io/_uploads/H1YTZC5Un.png) | 模式 | 語法指令 | | -------- | -------- | | 工作站| WiFi.mode(WIFI_STA); | | 熱點| WiFi.mode(WIFI_AP); | | 熱點+工作站| WiFi.mode(WIFI_AP_STA); | | 關閉網路功能| WiFi.mode(WIFI_OFF); | ### ESP32 WiFi 實作一: 啟動 ESP32 WiFi 功能,工作站模式 >1. 需 WiFi AP,供 ESP32 掃描 >1. 依課本 p.107,上傳程式 :::warning hint: >1. 程式先包含 library: >#include <WiFi.h> >1. 宣告為網路模式為 WiFi.mode() >1. 斷線、初始化為 WiFi.disconnect() >1. 宣告整數 n 為掃描的網路數量 int n = WiFi.scanNetworks() >1. 取得第 i 個網路的 SSID : WiFi.SSID(i) >1. 取得第 i 個網路的訊號強度:WiFi.RSSI(i) >1. 取得第 i 個網路的加密方式:WiFi.encryptionType(i) :::spoiler ```javascript= #include "WiFi.h" void setup() { Serial.begin(115200); // 設定網路模式為STA工作站模式 WiFi.mode(WIFI_STA); /* 括弧內可以輸入四種值,分別是: 1.WIFI_OFF (關閉WiFi) 2.WIFI_STA (無限終端) 3.WIFI_AP (wifi分享) 4.WIFI_AP_STA */ WiFi.disconnect(); /* 斷線,在 setup()裏為初始化的意思 */ delay(100); Serial.println("Setup done"); } void loop() { Serial.println("scan start"); //開始掃描附近網路 int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) { Serial.println("no networks found"); } else { Serial.print(n); Serial.println(" networks found"); //開始印出所有網路 for (int i = 0; i < n; ++i) { // Print SSID and RSSI for each network found Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(")"); Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*"); delay(10); } } Serial.println(""); //暫停5秒 delay(5000); } ``` ::: :::success 觀察:當開啟 Serial Monitor terminal 後,查看掃描結果。 ::: --- ## ESP32 接入 WiFi 和 http 網頁流程: ### 接入 WiFi 流程: | 動作 | 語法指令 | | -------- | -------- | | 模式宣告| WiFi.mode(WIFI_?); | | 初始化| WiFi.begin(ssid,password); | | | WiFi.begin(ssid, keyindex, key); | | 檢查狀態| WiFi.status()==WL_CONNECTED; | | 關閉網路功能| WiFi.mode(WIFI_OFF); | ### 接入 http 流程: | 動作 | 語法指令 | | -------- | -------- | | 宣告物件| HTTPClient http; | | 初始化| http.begin(url); | | | url = "http://網址" | | 檢查狀態| httpCode==HTTP_CODE_OK; | | 取得網頁內容| String payload=http.getString(); | 關閉網頁連線| http.end(); | ### 實作二: ESP32 連網,並讀取網頁資料 >1. 依課本 p.110,上傳程式 :::warning hint: >1. 啟動 WiFi 連線指令為 WiFi.begin(ssid, password), >1. 檢查 WiFi 連線狀態為 WiFi.status()==WL_CONNECTED >2. 網頁連線為 http.begin(URL), >3. 讀取網頁內容為 http.getString() :::spoiler ```javascript= #include <WiFi.h> #include <HTTPClient.h> char ssid[] = "你的SSID"; //請修改為您連線的網路名稱 char password[] = "你的密碼"; //請修改為您連線的網路密碼 //申請API key 環保署: https://data.epa.gov.tw/api-term //查看空氣品質列表:https://data.epa.gov.tw/api/v2/aqx_p_432?offset=0&format=json&api_key=你的APIkey String APIkey = "6b143ef9-f**************557c7ffa1c"; //你的環保署網站 API Key String Area = "橋頭"; //希望取得空氣品質的地點 String url = "https://data.epa.gov.tw/api/v2/aqx_p_432?format=json&limit=5&api_key=" + APIkey + "&filters=SiteName,EQ," + Area ; //PM2.5的網址 void setup() { Serial.begin(115200); Serial.print(" 開始連線到無線網路 SSID:"); Serial.println(ssid); // 1. 設定 WiFi 模式 WiFi.mode(WIFI_STA); // 2. 啟動 WiFi 連線 WiFi.begin(ssid, password); //3.檢查連線狀態 while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.print("連線完成 IP ="); Serial.println(WiFi.localIP()); } void loop() { //4.啟動網頁連線 HTTPClient http; // 宣告 http 物件 //http.setInsecure(); // 避免 SSL 問題 http.begin(url); // 設定網址 int httpCode = http.GET(); // 取得資料 //5.檢查網頁連線是否正常 if (httpCode == HTTP_CODE_OK) { // 如果取得資料成功 //6.取得網頁內容 String payload = http.getString(); // 取得資料內容 //7.將資料顯示在螢幕上 Serial.println(payload); } else { // 如果取得資料失敗 Serial.println("Error on HTTP request"); } //8.關閉網頁連線 http.end(); // 結束 http 連線 delay(10000); // 延遲 10 秒 } ``` ::: :::info [參考~[環保署API使用說明]~](https://data.epa.gov.tw/paradigm) 有關下載政府 epa 開放資料平台的資料,步驟如下: 1)請自行上網 API 介接服申請: https://data.epa.gov.tw/api-term 2)從登錄的 email,下載自己的 API key 3)政府 epa 開放資料平台 空氣品質指標(AQI)的網址 https://data.epa.gov.tw/api/v2/aqx_p_432?format=json&limit=5&api_key=您的API密鑰 其中 您的API密鑰 就是你的 API key ::: :::success 觀察:在 terminal 檢視顯示的網頁資料格式為何?哪些是需要的資料? ::: --- ### 實作三: 解析 JSON 網頁資料格式 >1. 依課本 p.113 安裝 ArduinoJson 函式庫 >1. 依課本 p.115,上傳程式 :::warning hint: >1. 理解 JSON object 以及 JSON array 的不同 >1. 宣告 Json 變數+大小 為 DynamicJsonDocument Variable(size) >1. 解析 Json 文件為 deserializeJson(xx,string) :::spoiler ```javascript= #include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h>//請先安裝ArduinoJson程式庫 char ssid[] = "你的SSID"; //請修改為您連線的網路名稱 char password[] = "你的密碼"; //請修改為您連線的網路密碼 //申請API key 環保署: https://data.epa.gov.tw/api-term //查看空氣品質列表:https://data.epa.gov.tw/api/v2/aqx_p_432?offset=0&format=json&api_key=你的APIkey String APIkey = "6b143ef9-f**************557c7ffa1c"; //你的環保署網站 API Key String Area = "橋頭"; //希望取得空氣品質的地點 String url = "https://data.epa.gov.tw/api/v2/aqx_p_432?format=json&limit=5&api_key=" + APIkey + "&filters=SiteName,EQ," + Area ; //PM2.5的網址 void setup() { Serial.begin(115200); Serial.print(" 開始連線到無線網路 SSID:"); Serial.println(ssid); // 1. 設定 WiFi 模式 WiFi.mode(WIFI_STA); // 2. 啟動 WiFi 連線 WiFi.begin(ssid, password); //3.檢查連線狀態 while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.print("連線完成 IP ="); Serial.println(WiFi.localIP()); } void loop() { //4.啟動網頁連線 HTTPClient http; // 宣告 http 物件 //http.setInsecure(); // 避免 SSL 問題 http.begin(url); // 設定網址 int httpCode = http.GET(); // 取得資料 //5.檢查網頁連線是否正常 if (httpCode == HTTP_CODE_OK) { // 如果取得資料成功 //6.取得網頁內容 String payload = http.getString(); // 取得資料內容 //7.將資料顯示在螢幕上 Serial.println(payload); //JSON格式解析 //payload = "[" + payload + "]"; //將資料轉為JSON 陣列格式 DynamicJsonDocument AQJarray(payload.length() * 2); deserializeJson(AQJarray, payload);//解析payload為JSON Array格式 String AQI = AQJarray["records"][0]["aqi"]; Serial.println(Area + " AQI:" + AQI); } else { // 如果取得資料失敗 Serial.println("Error on HTTP request"); } //8.關閉網頁連線 http.end(); // 結束 http 連線 delay(10000); // 延遲 10 秒 } ``` ::: :::success :wink: 延伸:在 terminal 顯示的資料有哪些?可以試試不同地點、資料種類。 ::: --- ## 多重 SSID AP 環境,選擇最強訊號連網: > 1. 當位處於多個 WiFi AP 的環境下,可以掃描周遭 AP 訊號。 > 2. 利用 WiFiMulti 功能,選擇有預設連結帳密的最強 SSID AP 連接。 ### 接入多重 WiFi 流程: | 動作 | 語法指令 | | -------- | -------- | | 宣告物件 wifiMulti|WiFiMulti wifiMulti;| | 模式宣告| WiFi.mode(WIFI_?); | | 增加 AP 帳密清單| wifiMulti.addAP("SSID_1", "password_1");| | 連線並檢查狀態| wifiMulti.run()==WL_CONNECTED | | 失去連線檢查 | wifiMulti.run(connectTimeoutMs) == WL_CONNECTED | ### 實作四: :::warning hint: >1. 程式先包含 library: >#include <WiFi.h> >#include <WiFiMulti.h> >1. 宣告物件 WiFiMulti wifiMulti; >1. 設定每一個 AP 的嘗試連接時間(ms) >const uint32_t connectTimeoutMs = 10000; >1. 宣告為網路模式為 WiFi.mode(WIFI_STA) >1. 增加 AP 帳密清單 >wifiMulti.addAP("SSID_1", "password_1"); >1. 連線並檢查狀態為 >wifiMulti.run()==WL_CONNECTED; >1. 宣告整數 n 為掃描的網路數量 >int n = WiFi.scanNetworks(); >1. 取得第 i 個網路的 SSID : WiFi.SSID(i); >1. 取得第 i 個網路的訊號強度:WiFi.RSSI(i); >1. 取得第 i 個網路的加密方式:WiFi.encryptionType(i); >1. 失去連線檢查: >wifiMulti.run(connectTimeoutMs) == WL_CONNECTED :::spoiler ```javascript= #include <WiFi.h> #include <WiFiMulti.h> WiFiMulti wifiMulti; // 設定每一個 AP 的嘗試連接時間(ms) const uint32_t connectTimeoutMs = 10000; void setup(){ Serial.begin(115200); delay(10); // 設定網路模式為STA工作站模式 WiFi.mode(WIFI_STA); // 增加 AP 帳密清單 wifiMulti.addAP("ssid_1", "password_1"); wifiMulti.addAP("ssid_2", "password_2"); wifiMulti.addAP("ssid_3", "password_3"); // //開始掃描附近網路 int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) { Serial.println("no networks found"); } else { Serial.print(n); Serial.println(" networks found"); for (int i = 0; i < n; ++i) { //開始印出所有網路 Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(")"); Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"); delay(10); } } // 以 wifiMulti 連訊號最強的 SSID AP Serial.println("Connecting Wifi..."); if(wifiMulti.run() == WL_CONNECTED) { Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } } void loop(){ //如果失去最強的 SSID 連網,則連下一個 AP 清單 if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) { Serial.print("WiFi connected: "); Serial.print(WiFi.SSID()); Serial.print(" "); Serial.println(WiFi.RSSI()); } else { Serial.println("WiFi not connected!"); } delay(1000); } ``` ::: :::success 觀察:當開啟 Serial Monitor terminal 後,查看掃描以及連線結果。 ::: --- ## ESP32 接入 WiFi 傳輸後,在C/S架構下,有兩種身份可以選擇: ![](https://hackmd.io/_uploads/S1h_DXPUn.png) > 1. 當 client,要求 server 提供服務 > 2. 當 server,提供 client 連線服務 | 身份 | 動作 | 語法指令 | | -------- | -------- | -------- | | client (客戶端) | 宣告物件 |WiFiClient client;| | |欲連接Server 之 ip 和 port| client.connect(ip,port);| | |client 停止連線 | client.stop();| | server (伺服端) | 宣告物件 |WiFi.server Server(port);| || server 開始提供連線 | Server.begin(); | || 宣告客戶端連線變數,並 listen | WiFiClient client = Server.available(); | || 檢查 client 連線狀態 | if (client) {}; | | |client 停止連線 | client.stop();| || server 停止連線服務 | Server.stop(); | ### 實作五: ESP32 以 WiFi工作站 + Server身份,建立有 HTTP 網頁的 Web Server 連線服務 ![](https://hackmd.io/_uploads/Hk4nt7DUn.png) >1. 將綠 LED 連接至 GPIO 15 位置 >1. 依課本 p.173,上傳程式 >1. 在 Serial terminal 取得 WebServer 的 IP address,並在相同網域電腦的 browser 訪問此 IP :::warning hint: >1. 開啟 server 的 http 網頁通訊特定埠 80: WiFiServer server(80) >1. ESP32 (server) WiFi 上網後的 IP address 為 WiFi.localIP(); >1. 宣告連線 WiFiClient client = server.available(); >初始值為"0",無 client 在線等待 >1. 檢查客戶端 client 是否連線 if (client) {}; >是否與上週學的 while(Serial.available()>0){} 和 while(BT.availabe()>0){} 有相似之處? >1. 在網頁列印 client.println("xxxx"); >是否與上週學的 Serial.println("xxxx"); 和 BT.print("xxxx"); 有相似之處? >1. 在網頁讀取 client.readStringUntil('\r'); >是否與上週學的 Serial.readStringUntil('\n'); 和 BT.readString(); 有相似之處? >1. 當網頁超聯結的「開啟綠色」字串被點擊時,client.readStringUntil('\r')會讀取字串 "GET/Gled=ON HTTP/1.1" >1. 再以 字串變數.indexOf("蒐尋字串")>=0 去做判斷蒐尋到的起始索引[從0開始,沒有發現時為 -1] :::spoiler ```javascript= #include <WiFi.h> //請修改以下參數-------------------------------------------- char ssid[] = "SSID"; char password[] = "SSIDpassword"; int Gled = 15; //宣告綠色Led在 GPIO 15 WiFiServer server(80); //宣告伺服器位在80 port void setup() { Serial.begin(115200); Serial.print("開始連線到無線網路SSID:"); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.println("連線完成"); server.begin(); Serial.print("伺服器已啟動,http://"); Serial.println(WiFi.localIP()); pinMode(Gled, OUTPUT); } void loop() { //宣告一個連線 WiFiClient client = server.available(); if (client) { //有人連入時 Serial.println("使用者連入"); //-------------網頁的html部份開始-------------- client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(""); client.println("<!DOCTYPE HTML>"); client.println("<html><head><meta charset='utf-8'></head>"); client.println("<br>"); client.println("<h1>ESP32 Web Server</h1>"); //HTML超連結指令 client.println("<a href='/Gled=ON'>開啟綠色LED</a><br>"); client.println("<a href='/Gled=OFF'>關閉綠色LED</a><br>"); client.println("</html>"); //-------------網頁的html部份結束-------------- //取得使用者輸入的網址 String request = client.readStringUntil('\r'); Serial.println(request); //判斷超連結指令 //網址內包含Gled=ON,就開啟綠燈,如果Gled=OFF,關閉綠燈 if (request.indexOf("Gled=ON") >= 0) { digitalWrite(Gled, HIGH); } if (request.indexOf("Gled=OFF") >= 0) { digitalWrite(Gled, LOW); } Serial.println("完成"); client.stop();//停止連線 } } ``` ::: :::success 觀察:點選網頁燈號超聯結,查看燈號變化以及Serial Monitor的輸出 ::: :::info ## :wink:延伸補充 >1. ESP32 以 WiFi AP + Server 身份,建立有 HTTP 網頁的 Web Server 連線服務時,有何不同? ![](https://hackmd.io/_uploads/HJU9aQvLh.png) 直接取得 ESP32 管理的資源(webServer背後的一顆LED燈而已),也同在一個網域內。 hint: const char* ssidAP = "xxxx"; //Esp32s基地台名稱 const char* passwordAP = "xxxxxxxx";//Esp32s基地台密碼(至少要8碼) WiFi.softAP(ssidAP, passwordAP); delay(500); IPAddress IP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(IP); server.begin(); [參考~[Esp32s用AP_mode+LED]~](https://ithelp.ithome.com.tw/articles/10262962) >2. 無線 AP 的差異: >WiFi天線的發射功率最高限制在 100mW(20dBm),手機則是 200mW(23dBm),所以手機天線有指向性 。 ![](https://hackmd.io/_uploads/BkrLO7OI2.png) ![](https://hackmd.io/_uploads/BJQc_mdLn.png) ![](https://hackmd.io/_uploads/rko2O7_I2.png) ![](https://hackmd.io/_uploads/B1V4tXu8h.png) ![](https://hackmd.io/_uploads/SJaHF7uLn.png) ![](https://hackmd.io/_uploads/Hy_DF7uU3.png) [參考~[Wi-Fi分享器的選購要點]~](https://my-best.tw/111) ::: :::info ## :bulb:家中的無線AP速度怎麼算? >1. 先了解802.11協定的規格: | 802.11協定 | 頻段GHz | 最高Mbps | MIMO支援 | | -------- | --------|--------|-------- | | - | 2.4 | 2 | N/A | | a | 5 | 54 | N/A | | b | 2.4 | 11 | N/A | | g | 2.4 | 54 | N/A | | n | 2.4/5 | 150 | 4, SU | | ac | 5 | 433.3 |8, DL MU | | ax | 2.4/5 | 600 |8, UL/DL MU| >2. 以 ac2100 規格: >屬於雙頻、 2x空間串流 802.11n + 4x 空間串流 802.11ac 產品,150Mx2 + 433Mx4 ≈ 2100M [參考~[你家的无线路由器速率咋算?]~](https://zhuanlan.zhihu.com/p/432434810) ::: ### 實作六: 把實作四放大三倍,使用 3 顆 LED 燈 >1. 將綠、黃、紅 LED 分別連接至GPIO 15、2、4 位置 >1. 依課本 p.177,上傳程式 :::spoiler ```javascript= #include <WiFi.h> //請修改以下參數-------------------------------------------- char ssid[] = "SSID"; char password[] = "SSIDpassword"; int Gled = 15; //宣告綠色Led在 GPIO 15 int Yled = 2; //宣告黃色Led在 GPIO 2 int Rled = 4; //宣告紅色Led在 GPIO 4 WiFiServer server(80); //宣告伺服器位在80 port void setup() { Serial.begin(115200); Serial.print("開始連線到無線網路SSID:"); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.println("連線完成"); server.begin(); Serial.print("伺服器已啟動,http://"); Serial.println(WiFi.localIP()); pinMode(Gled, OUTPUT); pinMode(Yled, OUTPUT); pinMode(Rled, OUTPUT); } void loop() { //宣告一個連線 WiFiClient client = server.available(); if (client) { //有人連入時 Serial.println("使用者連入"); //-------------網頁的html部份開始-------------- client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(""); client.println("<!DOCTYPE HTML>"); client.println("<html><head><meta charset='utf-8'></head>"); client.println("<br>"); client.println("<h1>ESP32 Web Server</h1>"); //HTML超連結指令 client.println("<a href='/Gled=ON'>開啟綠色LED</a><br>"); client.println("<a href='/Gled=OFF'>關閉綠色LED</a><br>"); client.println("<a href='/Yled=ON'>開啟黃色LED</a><br>"); client.println("<a href='/Yled=OFF'>關閉黃色LED</a><br>"); client.println("<a href='/Rled=ON'>開啟紅色LED</a><br>"); client.println("<a href='/Rled=OFF'>關閉紅色LED</a><br>"); client.println("</html>"); //-------------網頁的html部份結束-------------- //取得使用者輸入的網址 String request = client.readStringUntil('\r'); Serial.println(request); //判斷超連結指令 //網址內包含Gled=ON,就開啟綠燈,如果Gled=OFF,關閉綠燈 if (request.indexOf("Gled=ON") >= 0) { digitalWrite(Gled, HIGH); } if (request.indexOf("Gled=OFF") >= 0) { digitalWrite(Gled, LOW); } if (request.indexOf("Yled=ON") >= 0) { digitalWrite(Yled, HIGH); } if (request.indexOf("Yled=OFF") >= 0) { digitalWrite(Yled, LOW); } if (request.indexOf("Rled=ON") >= 0) { digitalWrite(Rled, HIGH); } if (request.indexOf("Rled=OFF") >= 0) { digitalWrite(Rled, LOW); } Serial.println("完成"); client.stop();//停止連線 } } ``` ::: :::success 觀察:點選網頁燈號超聯結,查看燈號變化以及Serial Monitor的輸出 ::: :::info 1. 現在可以在同一網域,上網遠距手動控制燈號了。 1. 若可以破除不同網域的限制,像不像外出時可以遠端遙控家中冷氣的雛形了呢? ::: --- ### 實作七: 請依課本 P.181,加上繼電器實作,體驗遠端控制電風扇開閉。 :::warning :warning: 注意 110V 的用電安全! ::: ## :bulb: 挑戰整合題: 合併上週所學 BT,完成可由 BT 和 WebPage 可雙向對 ESP32 傳送指令,控制三顆 LED 明滅 >1. 將綠、黃、紅 LED 分別連接至GPIO 15、2、4 位置 >1. 上傳程式 >1. App 連接 ESP32 藍牙裝置 >1. 對手機 App 麥克風說話 :::success 觀察:當語音指令正確接受後,查看燈號是否會變化 :::spoiler ### 思考題 程式碼(一) ```javascript= #include <BluetoothSerial.h> #include <WiFi.h> BluetoothSerial BT; WiFiServer server(80); //宣告伺服器位在80 port char ssid[] = "SSID"; //請修改為您連線的網路名稱 char password[] = "SSIDpassword"; //請修改為您連線的網路密碼 void setup() { Serial.begin(115200); Serial.println(""); Serial.print("Attempting to connect to WiFi AP "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println("WiFi AP is connected!"); server.begin(); Serial.print("Web Server is activated. Please visit at http://"); Serial.println(WiFi.localIP()); BT.begin("Richard6854"); //請改名 pinMode(15, OUTPUT); //綠LED pinMode(2, OUTPUT); //黃LED pinMode(4, OUTPUT); //紅LED } void loop() { //宣告一個連線 WiFiClient client = server.available(); if (client) { Serial.println("One visitor has logged in."); //---------網頁html開始------- client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); // client.println("Refresh: 10"); client.println(""); client.println("<!DOCTYPE HTML>"); client.println("<html><head><meta charset='utf-8'></head>"); client.println("<br>"); client.println("<h1>Web Server LED control console</h1>"); client.println(""); //HTML超連結指令 client.println("<a href='/Gled=ON'>開啟綠色LED</a><br>"); client.println("<a href='/Gled=OFF'>關閉綠色LED</a><br>"); client.println("<a href='/Yled=ON'>開啟黃色LED</a><br>"); client.println("<a href='/Yled=OFF'>關閉黃色LED</a><br>"); client.println("<a href='/Rled=ON'>開啟紅色LED</a><br>"); client.println("<a href='/Rled=OFF'>關閉紅色LED</a><br>"); client.println("</html>"); //-------------網頁的html部份結束-------------- //取得使用者輸入的網址 String request = client.readStringUntil('\r'); client.flush(); Serial.println(request); //判斷超連結指令 //網址內包含Gled=ON,就開啟綠燈,如果Gled=OFF,關閉綠燈 if (request.indexOf("Gled=ON") >= 0) { digitalWrite(15, HIGH); } if (request.indexOf("Gled=OFF") >= 0) { digitalWrite(15, LOW); } if (request.indexOf("Yled=ON") >= 0) { digitalWrite(2, HIGH); } if (request.indexOf("Yled=OFF") >= 0) { digitalWrite(2, LOW); } if (request.indexOf("Rled=ON") >= 0) { digitalWrite(4, HIGH); } if (request.indexOf("Rled=OFF") >= 0) { digitalWrite(4, LOW); } client.flush(); Serial.println("Client disconnected"); client.stop();//停止連線 } while (Serial.available()) { //讀取序列資列 String Sdata = Serial.readStringUntil('\n'); //傳輸給藍牙 BT.println(Sdata); //顯示在序列視窗 Serial.println(Sdata); if (Sdata == "1") {digitalWrite(15,HIGH);} if (Sdata == "2") {digitalWrite(15,LOW);} if (Sdata == "3") {digitalWrite(2,HIGH);} if (Sdata == "4") {digitalWrite(2,LOW);} if (Sdata == "5") {digitalWrite(4,HIGH);} if (Sdata == "6") {digitalWrite(4,LOW);} } delay(1); //檢查藍芽內是否有資料 while (BT.available()) { //讀取藍芽資料 String BTdata=BT.readString(); //顯示在序列視窗 Serial.println(BTdata); //檢查藍牙資料及相對的指示 if (BTdata == "1") {digitalWrite(15,HIGH);} if (BTdata == "2") {digitalWrite(15,LOW);} if (BTdata == "3") {digitalWrite(2,HIGH);} if (BTdata == "4") {digitalWrite(2,LOW);} if (BTdata == "5") {digitalWrite(4,HIGH);} if (BTdata == "6") {digitalWrite(4,LOW);} } delay(1); } ``` ::: --- ## :wink:延伸補充 除了使用 include <WiFiMulti.h> 外,另外有 WiFiManager 可讓 ESP32 Wifi 功能時,不需在程式裏,寫死SSID/Password: [參考 ~[ESP32WifiSmartConfig]~](https://github.com/poky/ESP32WifiSmartConfig?fbclid=IwAR1ohemddDLb1BGixc_t1_4as-3zsiH-M_b1jRKqaqrg_eQk7ReErgdg-I0) >透過 BLE 或 SoftAP 來當 Host 來接收帳密資訊 也可使用內建的 ESP-Touch 可透過 SmartConfig 來達成,把最基本的連線狀態判斷,儲存 NVR,以便重新通電後可直接連網,和如果找不到 SSID 的話會自動切換成beginSmartConfig 模式繼續接收等... --- :::info 下週教如何破除不同網域的限制,外出時可以實現遠端遙控家中的電器啟閉。 ::: ## 參考資料 >1)IOT物聯網應用第八、十二章 – 尤濬哲(夜市小霸王) 編著 >2)[LPWAN發展勢不可擋 LTE-M/NB-IoT並居主流技術2019-06-11 楊偉正](https://www.2cm.com.tw/2cm/zh-tw/tech/30D3FD9B745C4300AA2723720428DDE8) >3)[LoRaWAN™網路架構](https://www.allion.com.tw/certification/lora/) >4)[ESP32 WiFiMulti: Connect to the Strongest Wi-Fi Network (from a list of networks)](https://randomnerdtutorials.com/esp32-wifimulti/) >5)[MANET Network in Internet of Things System WRITTEN BY Rasa Bruzgiene, Lina Narbutaite and Tomas Adomkus](https://www.intechopen.com/chapters/53178) >6)[ESP32-CAM:为 Web 服务器(Arduino IDE)设置接入点(AP)](https://www.qutaojiao.com/24553.html) >7)[參考~[Esp32s用AP_mode+LED]~](https://ithelp.ithome.com.tw/articles/10262962) >8)[參考~[Wi-Fi分享器的選購要點]~](https://my-best.tw/111) >9)[參考~[你家的无线路由器速率咋算?]~](https://zhuanlan.zhihu.com/p/432434810) >10)[參考 ~[ESP32WifiSmartConfig]~](https://github.com/poky/ESP32WifiSmartConfig?fbclid=IwAR1ohemddDLb1BGixc_t1_4as-3zsiH-M_b1jRKqaqrg_eQk7ReErgdg-I0)