---
# System prepended metadata

title: '**[10] TCP/IP**'
tags: [單晶片助教]

---

---
tags: 單晶片助教
---
# **[10] TCP/IP**

## **課程大綱**
[ToC]

---
# **1. TCP/IP**

## 簡介

TCP/IP 是一種網際網路的基礎通訊架構，其內容主要分為 TCP (Transmission Control Protocol/傳輸控制協定) 還有 IP (Internet Protocol/網際網路協定)。

TCP/IP架構主要由四個層所組成，分別是：
1. Application Layer (應用層)
2. Transport Layer (傳輸層)
3. Internet Layer (網際網路層、網路層)
4. Network Access Layer (網路存取層)

通常我們在 messenger、Line 等傳送訊息，是在應用層輸入訊息，而這個訊息會經由傳輸層、網際網路層、網路存取層，一層層的將訊息打包成封包，並將這個封包發送出去。就好比在寫信時，必須寫好信件內容，並放入信封，寫上地址、收件者等資訊，才有辦法寄送出去。而 TCP/IP 四層架構，也是在做相同的事。

![](https://i.imgur.com/IhmFybI.png)


| 層級 | 協定種類 | 功能 |
| -------- | --------- | -------- |
| Application Layer (應用層) | HTTP、FTP、MQTT|定義應用專屬資料格式|
| Transport Layer (傳輸層) | TCP、UDP |定義傳輸方式|	
| Internet Layer (網際網路層) | IP |定義收發位置|	
| Network Access Layer (網路存取層) | MAC|提供定址及媒體存取的控制方式|	


其中，我們會較著眼在 Transport Layer (傳輸層)，以及 Internet Layer (網際網路層)。

**傳輸層 Transport Layer**
在傳輸層中有兩種比較常用的協定，分別為UDP (User Datagram Protocol)、TCP (Transmission Control Protocol)。其中以 TCP 為大宗。

大部分的網路協定都是建立在 TCP 協定之上，因為 TCP 有著高度可靠性。而 TCP 的高度可靠是建立在TCP的三次握手 (three-way handshake)規則上。當 TCP 在建立連接通道的過程，資料會互相來回傳送三次，以確保對方是否有接收到。


**網際網路層 Internet Layer**
在此層裡面，是將 IP 資料 (Internet Protocol) 再包裝到訊息封包內，而 IP 資料就是將訊息上標記，該訊息是從誰傳送出來，還有要將訊息傳給誰。

常見的IP協定有兩種，一是 IPv4，二則是 IPv6，兩者最大的差別在於 IPv4 是 32bits，IPv6 則是 64bits。

此協定內有一段資料是 IP Address，IP Address 就是說明該訊息是從哪個位置傳送出來的。其中 IP 有分為以下幾種類型：

固定 IP：固定的 IP，企業、學校等都是使用固定 IP，這樣方能確保使用者可以連上伺服器。
浮動 IP：在每次連網的時的 IP 位置都不一樣，一般的使用者跟家用電腦大部分都是浮動 IP。
虛擬 IP：僅能使用於內部網路 (或是區域網路)，外網是連不上的。


# **2. Wi-Fi**

WiFi.h 為 Arduino IDE 內建程式庫，提供 4 種 WiFi 模式可用:

| 語法 | WI-FI模式 | 功能 |
| -------- | -------- | -------- |
| WiFi.mode(WIFI_AP); | Access Point (AP) | ESP32 可以讓其他設備透過 Wi-Fi 接入(就像家裡的 Wi-Fi 基地台，可供手機連線)。 |	
| WiFi.mode(WIFI_STA); | Station(STA) | 無線終端模式，也就是讓 ESP32 可以連接上其他的熱點(就像手機一樣，可以連上家裡 Wi-Fi)。 |
| WiFi.mode(WIFI_AP_STA); | AP+STA | 將 ESP32 設置成兩個模式並存。 |
| WiFi.mode(WIFI_OFF); | OFF | 關閉 Wi-Fi。 |

## 將 ESP32 連接至 Wi-Fi
* 程式碼：
```c++=
#include <WiFi.h> //Wi-Fi 程式庫

const char* ssid = "Wi-Fi 網路名稱"; //Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; //Wi-Fi 密碼

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA); //設置 Wi-Fi 模式
  WiFi.begin(ssid, password); 

  Serial.print("Wi-Fi Connecting");

  //當 Wi-Fi 連線時會回傳 WL_CONNECTED，因此跳出迴圈時代表已成功連線
  while(WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(500);   
  }

  Serial.println("");
  Serial.print("Wi-Fi Connected.");
}

void loop() {
}
``` 


## Lab 1-1:
  ### 獲取網路時間
  * 實驗目的：學習於 ESP32 上使用 Wi-Fi。
   
  * 實驗目標：透過 Wi-Fi 取得網路時間，並顯示於 LCD 螢幕上。
  
  * 範例程式碼：
  ```c++=
#include <WiFi.h> //Wi-Fi 程式庫
#include <Wire.h> //I2C 程式庫
#include <LiquidCrystal_I2C.h> //LCD_I2C 模組程式庫

LiquidCrystal_I2C lcd(0x27, 16, 2);

const char* ssid = "Wi-Fi 網路名稱"; //Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; //Wi-Fi 密碼
const char *ntpServer = "pool.ntp.org"; //校時伺服器
const long  gmtOffset_sec = 8*3600;  //格林威治時間, GMT+8 就是 8*3600
const int   daylightOffset_sec = 0; //日光調節時間, 有為 3600, 沒有則為 0

void setup(){
      Serial.begin(115200);

      //初始化LCD
          //To do.
          
      //Connect to Wi-Fi
          //To do.
      
      //取得網路時間
      configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
      printLocalTime();
      
      //連上拿到時間資料後就切斷連線
      WiFi.disconnect(true);
      WiFi.mode(WIFI_OFF);
}

void loop(){
      delay(1000);
      printLocalTime();  
}

void printLocalTime(){
      struct tm timeinfo;
      if(!getLocalTime(&timeinfo)){
        Serial.println("Failed to obtain time");
        return;
      }
      Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  
      //顯示於 LCD 螢幕
          //To do.
}
  ``` 
  * 結果：
  <iframe width="560" height="315" src="https://www.youtube.com/embed/eKa7FWkbXng""YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## LINE Notify
![](https://i.imgur.com/aZh10ba.png)

* **LINE Notify 是一項非常方便的服務，用戶可以透過 LINE 接收GitHub、IFTTT 及 Mackerel 等網站服務的提醒。與網站服務連動完成後，LINE 所提供的官方帳號「LINE Notify」將會傳送通知。**

### 登入 Line Notify 取得 Token
* 請先打開瀏覽器，並搜尋Line Notify，進入頁面後點擊登入。

![](https://i.imgur.com/8f7Ltew.png)

* 登入後，點擊相同地方，選擇個人頁面。

![](https://i.imgur.com/yEbzz1V.png)

* 點擊發行權杖。

![](https://i.imgur.com/EcmcWGR.png)

* 當出現這畫面時，就是開始申請權杖 (Token)，所以請先輸入名稱，及選擇透過 1 對 1 聊天接收 Line Notify 的通知，都選擇好後點擊發行。

![](https://i.imgur.com/wI410P2.png)

* 發行後，會看到一條序號，這序號就是你的權杖(Token)，請先把這權杖複製起來，之後會使用到。

![](https://i.imgur.com/fzaLfXP.png)


**<font color="Red">注意，該權杖只會顯示這次，所以請先把這權杖記錄起來，盡量複製起來貼上記事本上</font>**

### Arduino IDE 取得 TridentTD_LineNotify 程式庫
* 至程式庫管理員下載 "TridentTD_LineNotify"，版本為 3.0.3 **<font color="Red">其餘版本會導致無法編譯。</font>**

![](https://i.imgur.com/AfFuFMS.png)

### Arduino IDE 取得 DHT11 程式庫
* 至程式庫管理員下載 "DHT sensor library"，使用版本為 1.4.4。

![](https://hackmd.io/_uploads/SyCIMvvE3.png)


* 範例程式碼：
```c++=
#include <WiFi.h> //Wi-Fi 程式庫
#include <TridentTD_LineNotify.h> //Line 程式庫

#define LINE_TOKEN "xxxxxxxxxxx" //修改成上述的 Token 號碼

const char* ssid = "Wi-Fi 網路名稱"; //Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; //Wi-Fi 密碼
 
void setup(void)
{
  Serial.begin(115200);

  WiFi.mode(WIFI_STA); //設置 Wi-Fi 模式
  WiFi.begin(ssid, password); 

  Serial.print("Wi-Fi Connecting");

  //當 Wi-Fi 連線時會回傳 WL_CONNECTED，因此跳出迴圈時代表已成功連線
  while(WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(500);   
  }

  Serial.println("");
  Serial.print("Wi-Fi Connected.");
  
  Serial.println(LINE.getVersion());  //顯示 Line 版本
  LINE.setToken(LINE_TOKEN);    //設定要傳送至的聊天室 Token
}
 
void loop(void)
{
  //每一分鐘 (60000ms)，傳送 "Hello" 至 Token 指定的聊天室
  LINE.notify("Hello");
  delay(60000);
}
```


## Lab 1-2:
  ### IOT 物聯網
  * 實驗目的：學習於 ESP32 上使用 Wi-Fi，並透過 Line 傳送訊息。
  
  * 實驗目標：使用 DHT11 讀取環境溫濕度，並透過 Wi-Fi 將訊息傳送至 Line。
  
  * 接線圖: 
  ![](https://i.imgur.com/lAmREQJ.png)
  * **DHT11 與其他感測器相比較為脆弱，若 VCC、GND 接反很快就會燒毀，在接線上必須特別注意**

  * 程式碼:
  <font color="Red">下方僅給予溫度的程式碼，需自行加入濕度的程式碼</font>
  ```c++=
#include <WiFiClient.h> //WiFi 程式庫
#include <Wire.h> // I2C 程式庫
#include <TridentTD_LineNotify.h> //Line 程式庫
#include "DHT.h"  // DHT11 程式庫

#define LINE_TOKEN "xxxxxxxxxxx" //修改成上述的 Token 號碼
#define DHTTYPE DHT11
#define DHTPIN 12

const char* ssid = "Wi-Fi 網路名稱"; //Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; //Wi-Fi 密碼

float humidity, temp_c; //從 DHT11 讀取的值

DHT dht(DHTPIN, DHTTYPE); //啟動 DHT 程式庫

void setup(){
      Serial.begin(115200);
      dht.begin();

      // Connect to Wi-Fi
        // To do.

      Serial.println(LINE.getVersion());  //顯示 Line 版本
      LINE.setToken(LINE_TOKEN);    //設定要傳送至的聊天室 Token
}

void loop(){
    //讀取環境溫濕度
      temp_c = dht.readTemperature(); //讀取溫度(攝氏)
      //To do.
      
    //傳送至 Token 的聊天室
      //To do.
}
  ``` 
  * 結果:
  
  ![](https://i.imgur.com/xNFLZpu.png)  
  
# **3. ESP32 網頁應用**
## Http 介紹
超文字傳輸通訊協定 (HTTP) 是針對在網路上傳送內容而設計的通訊協定。 HTTP 是簡單的通訊協定，利用可靠的傳輸控制通訊協定 (TCP) 服務來執行其內容傳輸功能。 因此，HTTP 是高度可靠的內容傳輸通訊協定。 HTTP 是其中一個最常使用的應用程式協定。 Web 上的所有作業都會使用 HTTP 通訊協定。

![](https://i.imgur.com/7OY3kOx.jpg)

使用者會發出 HTTP Request 跟伺服器索取資料，伺服器會回傳 HTTP Response 告知使用者網頁讀取的狀態
* HTTP Resuest Headers 儲存每個 HTTP Request 的核心資訊，例如：版本、語言、網址、編碼方式
* HTTP Resuest Method 告知伺服器要執行的動作，有 8 種動作可執行：
  * OPTIONS
  * GET：期望返傳回資訊（通常以網站的形式）
  * HEAD
  * POST：表示用戶端正在向伺服器提交資訊（例如表單資訊，如提交的使用者名稱和密碼）。
  * PUT
  * DELETE
  * TRACE
  * CONNECT
  最常使用的是 GET、POST
* HTTP Response Status 告知 HTTP Resuest 是否完成的狀態代碼
  * 1xx 資訊內容
  * 2xx 成功
  * 3xx 重新導向
  * 4xx 用戶端錯誤
  * 5xx 伺服器錯誤

## Lab 2-1:
### ESP32 Wi-Fi Server
* 實驗目的：學習使用 ESP32 建立 Wi-Fi server
* 實驗目標：使用 DHT11 讀取環境溫濕度，並使用 ESP32 建立 server，使其他內網裝置的瀏覽器可觀看溫濕度資訊。
* 接線圖: 

![](https://i.imgur.com/lAmREQJ.png)

* **DHT11 與其他感測器相比較為脆弱，若 VCC、GND 接反很快就會燒毀，在接線上必須特別注意**

* 使用 Library
  * WiFi.h
  * ESPAsyncWebServer.h
    * 不需要更新整個網頁就對局部數值更新
    * 避免網頁收發延遲造成卡死
    * 可以同時處理多個連線的收發
    * 因為是非同步伺服器，連線部分不寫在 loop 裡，而是獨立運作的執行緒
    * 不可使用讓整個程式停止運作的函數，例如：delay()，若要間隔一段時間要使用 millis()
    * 一個request只會有一個response
    * [程式庫連結](https://github.com/me-no-dev/ESPAsyncWebServer)
  * AsyncTCP
    * 配合 ESPAsyncWebServer.h 使用
    * [程式庫連結](https://github.com/me-no-dev/AsyncTCP)
  *  DHT.h
  
* 程式碼:
  <font color="Red">下方僅給予溫度的程式碼，需自行加入濕度的程式碼</font>
```c++=
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <DHT.h>

// 定義 DHT 資料傳輸的腳位
#define DHTPIN 23

// 選擇使用的 DHT 溫溼度計種類
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE); // 啟動 DHT 程式庫

const char* ssid = "Wi-Fi 網路名稱"; // Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; // Wi-Fi 密碼

// 初始化溫度與濕度的變數
float t = 0.0;
float h = 0.0;

// 建立一個非同步伺服器在通訊埠 80
AsyncWebServer server(80);

// 紀錄量測間隔時間，因為數值較大，用 unsigned long
unsigned long previousMillis = 0;    

// 每 n 秒量測，1000->1秒，2000->2秒
const long interval = 2000;

// 建立網頁
// 網頁大小相對大但是架構不會變，一般存變數的動態記憶體不足
// 要存在儲存程式程序的flash記憶體中，所以呼叫PROGMEM
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!--設定字體-->
  <style>   
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p  { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>
<!--建立網頁內容-->
<body>    
  <h2>ESP32 DHT11 Server</h2>
  <p>
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  
</body>
<script>
setInterval(function ( ) {    <!--將溫度資料傳入伺服器顯示在瀏覽器上-->
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 1000 ) ;


</script>
</html>)rawliteral";

// 抓取最新的溫溼度資料
String processor(const String& var) {
  //Serial.println(var);
  if (var == "TEMPERATURE") {
    return String(t);
  }

  return String();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  
    // Connect to Wi-Fi
      // To do.
      
    // 查詢 ESP32 的 local IP 並印出，在瀏覽器輸入此 IP 即可看到畫面
      // To do.
  
  server.begin();

  // 在伺服器上建立網頁
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html, processor);
  }); //建立主網頁
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/plain", String(t).c_str());
  }); //建立溫度顯示的網頁

  server.begin();  // 啟動伺服器
  dht.begin();  // 啟動 DHT 感測器
}

void loop() {
  // 建立量測溫溼度的迴圈
  // 因為網頁伺服器不能使用 delay 函數，所以用 millis
  // millis 用運算的次數來計算時間，更為準確，也不會阻擋程式運作
  // delay 函數運作時會完全停止程式運作，使網頁伺服器出錯
  
  //使用 DHT11 讀取環境溫濕度
  //使用 millis 做間隔，每 2 秒讀一次
    //To do. 

    t = temperature; // t 為要給 server 的溫度變數，temperature 來自 DHT11 的讀值
    Serial.print(F("Temperature: "));
    Serial.println(t);
  }
}

```

## Lab 2-2:
### ESP32 外網連接
在 Lab2-1 中使用 ESP32 建立的 server 僅能用於內網 (同一個路由器底下的網路)，為了使其他不同網域的裝置也能使用到 ESP32 作為處理器的資料，我們這邊會使用到另一種網路協定：MQTT

#### MQTT (Message Queuing Telemetry Transport)
* 以 TCP/IP 架構傳輸，專門用在效能低與網路不佳的環境
* MQTT Broker 作為伺服器，其他裝置作為 client 發送與訂閱訊息
* 資訊的傳輸是通過 topic 管理的。發布者發送資料給 broker 時會寫下資料的 topic，broker 則向訂閱此 topic 的客戶端分發此資料
* 發布者與訂閱者不需知道彼此是誰
* 可跨網域遠端運作

![](https://i.imgur.com/3jYjIfw.png)

#### QoS (Quality of Service)
* 決定資料傳輸的驗證方式，會影響傳輸效能

| 服務品質  | 傳輸方式 | 說明 |
| -------- | -------- | -------- |
| QoS0    | 最多一次傳輸  | 只負責發送，送後不理 |
| QoS1    | 至少一次傳輸  | 確認資料發送，確認前不停發送 |
| QoS2    | 正好一次傳輸  | 保證資料交付成功，且只發送一次 |

#### Retained Message
* Topic 會保存最新的訊息，確保新連上或重連的訂閱者都能收到最新的訊息

* 使用Library
  * WiFi.h
  * PubSubClient.h
    * 建立 MQTT 連線
  *  DHT.h
  
* 程式碼:
  <font color="Red">下方僅給予溫度的程式碼，需自行加入濕度的程式碼</font>
```C++=
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>

#define LED 2        //使用 ESP32 內建 LED

#define DHTPIN 23     // 定義 DHT 資料傳輸的腳位

// 選擇使用的 DHT 溫溼度計種類
#define DHTTYPE DHT11     // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE); // 啟動 DHT 程式庫

// 紀錄量測間隔時間，因為數值較大，用 unsigned long
unsigned long previousMillis = 0;

// 每 n 秒量測，1000->1秒，2000->2秒
const long interval = 2000;


const char* ssid = "Wi-Fi 網路名稱"; // Wi-Fi 網路名稱
const char* password = "Wi-Fi 密碼"; // Wi-Fi 密碼
 
// 建立 MQTT 連線所需的 client ID，可使用隨機生成
String mqtt_ClientID = "mqttClientID";  
 
// 定義 MQTT 連線時，用來識別 publish/subscribe 資料類別的 topic
// 請依照組別更改groupX
const char* sub_topic = "groupX/esp32mqtt";
const char* pub_led_topic = "groupX/esp32s_led_state";  // 控制 ESP32 上的 LED
const char* pub_init_topic = "groupX/esp32s_is_back";   // 確認 ESP32 與 MQTT broker 已連線
const char* pub_temp_topic = "groupX/esp32s_temp";      // 傳輸溫度

 
// EMQX MQTT broker 的連線資訊
const char *mqtt_server = "broker.emqx.io";  // broker 所在的網址
const char *mqtt_userName = "emqx";  // EMQX broker 的公用帳號
const char *mqtt_password = "public";  // EMQX broker 的公用密碼

// 初始 Wi-Fi client 與 MQTT client 連線
WiFiClient espClient;
PubSubClient client(espClient);

// 初始化儲存溫度與濕度的變數
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];


 
void setup_wifi() { // 設定 Wi-Fi 連線
  delay(10);
  
   // Connect to Wi-Fi
      // To do.

  randomSeed(micros());

  // 連線成功後將連線的 IP 資訊印出
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

//接收 MQTT broker subscribe 來的資訊 (LED 亮暗)
void callback(char *topic, byte *payload, unsigned int length) { // 資訊包含 topic 與 topic 內的資料
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  payload[length] = '\0';
  String message = (char *)payload;  // 儲存 topic 內的資料 payload 到 message 裡

  // 驗證傳來的 topic 是我定義中要求的 topic
  // strcmp 會比較()內兩個字串，相同的話回傳 0，不同的話會輸出差幾個字母數，並用正負表示哪邊比較大
  if (strcmp(topic, sub_topic) == 0) {
    if (message == "off") {
      digitalWrite(LED, LOW);  // 收到 off 關掉 ESP32 的 LED
      client.publish(pub_led_topic, "off");
    }
    if (message == "on") {
      digitalWrite(LED, HIGH);  // 收到 on 開啟 ESP32 的 LED
      client.publish(pub_led_topic, "on");
    }
  }
}
 
void reconnect() {
  // 對 MQTT 進行連線建立，若未建立成功會持續在此循環
  while (!client.connected()) {
    Serial.println("Attempting EMQX MQTT connection...");
    // 用亂數確保建立唯一的 ClientID
    mqtt_ClientID += String(random(0xffff), HEX);
    // 嘗試建立連線
    if (client.connect((mqtt_ClientID, mqtt_userName, mqtt_password)))
    {
      Serial.print(" connected with Client ID: ");
      Serial.println(mqtt_ClientID);
      // 若建立成功，可在其他裝置訂閱 pub_init_topic 收到 "Hi, I'm online!" 的訊息
      client.publish(pub_init_topic, "Hi, I'm online!");
      // 重新接收資料
      client.subscribe(sub_topic);
    }
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      // 可於此查詢連線失敗原因編碼
      // https://github.com/knolleary/pubsubclient/blob/master/src/PubSubClient.h
      Serial.println(" try again in 5 seconds");
      // 若 MQTT 連線失敗，每 5 秒重試
      delay(5000);
    }
  }
}
 
void setup() { 
  dht.begin();  //啟動 DHT 感測器

  pinMode(LED, OUTPUT);  // 將 LED Pin 腳位設為輸出
  digitalWrite(LED, LOW);  // 將 LED Pin 腳位設為數位輸出(0V/5V)，並預設為低(0V)

  Serial.begin(115200);
  
  setup_wifi(); // 進入設定 Wi-Fi 連線的 void function
  
  client.setServer(mqtt_server, 1883); // 設定 MQTT broker 的連線
  client.setCallback(callback);  // 接收從其他 MQTT client 傳來的資料
}
 
void loop() {
  
  if (!client.connected())  {  // 若 MQTT 連線建立失敗就重新連線
      reconnect();
  }
  client.loop();


  //使用 DHT11 讀取環境溫濕度
  //使用 millis 做間隔，每 2 秒讀一次
    //To do. 
  if (......)  {
    // 如果上次時間點到這次時間點已經大於間隔時間，就開始量測
    // 將上次的時間記錄更新成這次的時間點儲存
    
    // 使用 DHT11 讀取環境溫濕度
    
    if (......) { // 如果讀取到的溫溼度為空，表示 DHT11 有問題
      Serial.println(F("Failed to read from DHT sensor!"));
      return;
    }
    else { // 如果溫溼度正常就publish給MQTT broker
      snprintf(msg, MSG_BUFFER_SIZE, "%.1lf°", temperature); //印出溫度

      Serial.print("Publish message: ");
      Serial.print(msg);

      client.publish(pub_temp_topic, msg);  // 綁定 topic，發送溫度     
    }
  } 
}
```

* 於電腦上開啟 MQTT 方式
Chrome 搜尋 MQTT Lens 並安裝

![](https://i.imgur.com/aaPWd4B.png)

點選新增 "+"

![](https://i.imgur.com/O0kSkaS.png)

輸入 MQTT Broker 連線資訊

![](https://i.imgur.com/CoUy4f6.jpg)


訂閱存有溫溼度資訊的 topic

![](https://i.imgur.com/iWgtlj7.png)

* 於 Android 手機上開啟 MQTT 方式
Play 商店搜尋 MQTT Dash 安裝

![](https://i.imgur.com/4drd96S.png)

訂閱存有溫溼度資訊的 topic

![](https://i.imgur.com/NPIJBIP.png)

訂閱開關 LED 的 topic

![](https://i.imgur.com/5K1tqIO.png)


## Lab3

![](https://hackmd.io/_uploads/By5nxdPNh.png)


* 實驗目的：將 ESP32 與 Hololens MR 眼鏡透過 TCP/IP 連接，並在 MR 眼鏡上玩彈跳球小遊戲。

* 實驗目標：將 ESP32 作為 TCP/IP server，接兩顆按鈕，代表方向鍵左與右，向 client 端輸出 "LXRX" 字串，以 0/1 代表左右按鈕是否被按下，如 "L1R0"，"L0R1"。

* 遊戲畫面：{%youtube liImljSbvM4%}

```C++=
#include <WiFi.h>


int port = 8888;  //Port number
WiFiServer server(port);

//Server connect to WiFi Network
const char *ssid = "";  //Enter your wifi SSID
const char *password = "";  //Enter your wifi Password


void setup() 
{
  Serial.begin(115200);

  Serial.println();

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password); //Connect to wifi
 
  // Wait for connection  
  Serial.println("Connecting to Wifi");
  while (WiFi.status() != WL_CONNECTED) {   
    delay(500);
    Serial.print(".");
    delay(500);
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);

  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  
  server.begin();
  Serial.print("Open Telnet and connect to IP:");
  Serial.print(WiFi.localIP());
  Serial.print(" on port ");
  Serial.println(port);

}

void loop() 
{
 /*do something*/
 
 
  WiFiClient client = server.available();

  //Serial.println(sendData.c_str());
  if (client) {
    if(client.connected())
    {
      Serial.println("Client Connected");
    }
    
    while(client.connected()){ 
        /*do something*/
        
    }
    client.stop();
    Serial.println("Client disconnected");    
  }
}
```


 
# 課後問題 (3 題)
:::info
* **Q1. 請簡述什麼是 TCP/IP？**
* **Q2. 請簡述 HTTP 與 HTTPS 的差別。**
* **Q3. 請簡述什麼是 MQTT？**

**回答請勿直接複製上述講義內容!**

:::
:::success
## 作業繳交格式 

**W10結報_第XX組.zip**
壓縮檔裡包含:
1.W(週數)結報.pdf
2.資料夾:W(週數)
    Lab1.ino
    Lab2.ino
    ...


:::