---
# System prepended metadata

title: 畢業專題 - 智慧家居系統 支援多品牌家電整合
tags: [專題]

---

# 畢業專題 - 智慧家居系統 支援多品牌家電整合
[toc]

---

Repo：https://github.com/nora6633/HomeAssisstantIntegration

#### APP (Client) - Server 傳訊流程
APP(ex:手機上) 要用來傳語音給 server (ex:樹梅派)。
要傳訊給 server 有很多協定可傳送語音訊息(MQTT是其中一種)，可以使用一些基於 MQTT 這協定去開發的平台(ex: mosquito或Kafka)
> APP 會送 mosquito 的 message 給 mosquito 的 message queue

在樹梅派上裝一個 mosquito server，之後只要從手機APP(client) 傳 mosquito 的語音訊息給 mosquito server。

python 之後只要去跟 mosquito server 拿資料就好了( python 要去訂閱 mosquito topic)

#### MQTT 
是一個輕量的通訊協定。

優點:
- 適合網路狀況差、需低耗電的設備
- 封包小 (header 最小只有 2 byte)
- keep connection，不需重新建立連線
- 支援多種語言的實作
- 可以有帳號密碼機制
    - ![image](https://hackmd.io/_uploads/SJgh892pT.png)
- mqtt 是預設明文傳輸，所以可以搭配ssl加密
    > ref: https://officeguide.cc/mqtt-broker-server-mosquitto-installation-tutorial-examples/

#### QOS (Quality of service)

QoS 是 MQTT 內建去應付需要高可靠性的訊息傳輸

有3種 QOS:

0. 可能曾沒收到
1. 一定曾收到，但可能重複
2. 保證收到且不重複(consumer 可能會出現漏接封包的狀況)

-> 可以考慮加上Kafka，因為 MQTT 沒有暫存機制，如果中間斷線資料會收不到。

> 需要考慮需不需要這麼高的可靠性，有些資訊其實沒那麼重要，lost一筆也沒差。

#### Kafka
可以用 Kafka 去管理所有訊息的交換，最終用 MQTT 送到終端設備上。
也就是把 Kafka 作為 MQTT 的 broker。
> Kafka 是軟體(就是原本 mosquito 的角色)，跟 MQTT 是不同層
級的東西

優點:
Kafka 有資料庫去暫存資料，它底層的設計讓 consumer 比較不會lost 封包，因為 comsumer 會確認是否接收到每一個封包後，才會接收下一個封包(有順序的問題，避免 lost 某個封包)。
> 用 NATS 也不錯

MQTT適用明文傳輸，適合非常大量傳訊的狀況(ex:抓及時 GPS 資料)，然而 MQTT 比較容易miss資料。

--- 

#### Home Assistant (HA)
開源的智慧家庭自動化平台，允許用戶通過界面控制設備。

優點:
- 高度整合性: 整合智慧家庭設備和服務。
- 自動化和腳本: 支援自動化規則和腳本，用戶可自訂智慧家庭的行為。
- 開源: 有活躍的開源社區開發新的整合和功能。
- 本地處理: 運行在本地(不依賴雲服務)，增強隱私保護和可靠性。

缺點:
- 需要一定的技術知識: 對於初學者來說，配置和設置比較複雜。充分利用其功能需要一定程度的技術知識。

#### HA支援什麼樣的協定
> 老師有提到CoAP、MQTT、Matter、SDP。

各協議有其特點和應用場景(ex:通信範圍、數據傳輸率、能耗限制、安全性要求)。設計智慧家庭系統時，需結合使用多種協議以滿足不同設備和應用的需求。

#### 較輕量的
較流行，HA也比較好支援: MQTT、Matter、Zigbee、Wifi、藍芽、 Thread

> Websocket不夠輕量，無法支援網路環境糟的場景，且效能也需消耗很大。




:::spoiler
- Zigbee: 低功耗無線通訊協議，用於短距離，適合於建立低功耗的個人區域網絡。

- Z-Wave: 低功耗的無線通信協議，用於家庭自動化，能夠讓設備彼此通信和中繼信號。

- Bluetooth Low Energy (BLE): 是藍牙技術的一個版本，專為低功耗操作設計，適合於短距離通信。

- Thread: 基於IPv6的無線網絡協議，提供可靠的、安全的和低功耗的網絡連接。用於連接和控制家庭中的大量低功耗設備。

- WebSocket: 提供全雙工通信通道的協議，經常用於網頁客戶端和伺服器之間的即時通信。

- HTTP/HTTPS: 超文本傳輸協議，是網絡上資料通信的基礎，用於智慧家庭設備的遠程訪問和控制。

- OPC UA (Open Platform Communications Unified Architecture): 一種跨平台的標準通信協議，用於工業自動化領域，提供了數據收集、管理和交換的安全可靠方式。
:::


## API、Docker

新增部分：

1. API 如何判斷是否需要使用 OpenAI
2. API 要產出讓 Home Assistant 讀懂的 YAML
3. 把存到本地的 YAML 軟連結到 Docker 


### 判斷是否需要使用 OpenAI
判斷是否需要依賴 OpenAI 的進階處理能力，或直接通過 Home Assistant 來處理指令的時機

* 定義明確指令規則：
    1. 先設定一個基本的指令集，這些指令 Home Assistant 可以直接處理
        > 例如:開燈、關電視
    2. 如果 user 的輸入直接匹配到預設的指令，就直接讓 Home Assistant 處理。


* 意圖識別（Intent Recognition）：
    1. 當輸入不是預設指令，用意圖識別分析語句的目的。通常需用自然語言處理，也就是NLP技術（這部分可以透過 OpenAI 做）。
     例如: 比較含糊或多功能的指令（ex:我覺得有點冷），可能代表 user 想調低冷氣溫度，這時候就需要 OpenAI 。
    > 用語音向智能助理講話時，助理用 NLP 理解並作出回應
    > （NLP 讓電腦能夠讀懂和解釋人類的文字或語音）

* 設計回落機制（Fallback Mechanism）：
    1. 設計一個機制， 當 Home Assistant 無法直接理解或執行指令時，自動將 request 轉給 OpenAI 處理。
    2. 設定一定的置信度閾值（Confidence Threshold）
    3. 若 Home Assistant 對於指令的解析置信度低於此值，則求助 OpenAI 
    
    > 當功能失效或無法正常執行時(ex:遇到一個不懂的複雜指令)，系統可以啟用回落機制來保證系統還能以另一種方式繼續運作
    > (ex: 尋求更先進的 NLP 處理，或是要求 user 重新輸入)
    
    > 置信度閾值是用來判斷系統對其自身處理結果的確信程度。（ex: 有中一個關鍵字加一分）
    > ex: 語音識別時，系統會對它對話語的解析結果有一個“置信度評分”。如果分數低於設定的“閾值”，就判斷它對指令的理解不夠確定，就啟動回落機制。（要求 user 重新說明，或使用更複雜的 NLP 處理來嘗試理解指令...等）

---

### 產出能讓 Home Assistant 讀懂的 YAML
寫一個 API，以處理語音輸入並產出能讓 Home Assistant 讀懂的 YAML 指令(以下先用 Python 說明，因為它在表示這部份更簡單易懂）

> 基礎流程：
>  1.接收文本
> 2.解析意圖
> 3.生成 Home Assistant 可以理解的指令。

1. 安裝 Flask 來建立一個簡單的 Web API，並安裝 PyYAML 來處理 YAML 格式的數據。
```
pip install flask pyyaml
```

2. 建立 Flask API
用來接收語音輸入的文字，處理後會產出 YAML 格式的指令。

```python=
from flask import Flask, request, jsonify
import yaml

app = Flask(__name__)

@app.route('/api/command', methods=['POST'])

def write_yaml_to_file(yaml_content):
    # 需要有寫入權限
    with open('output.yaml', 'w') as file:
        file.write(yaml_content) # Home Assistant 需要能讀到這檔案
        
def generate_command():
    # 從 request 獲取語音轉換後的文字
    data = request.json
    text = data['text']

    # 從文本內容生成對應的 Home Assistant 指令
    command = parse_text_to_command(text)

    # 將指令轉換成 YAML 格式
    yaml_command = yaml.dump(command)
    
    write_yaml_to_file(yaml_command)
    
    return Response("YAML file has been created", mimetype='text/plain')

    # 讓其他服務（ex:前端）或開發者使用這個 API 更方便地接收和處理數據
    # return jsonify(yaml_command=yaml_command) 

# 可以根據之後的需求去調整邏輯，來處理更複雜或更具體的指令
def parse_text_to_command(text):
    # 舉例一下
    if "開燈" in text:
        return {"entity_id": "light.living_room", "service": "turn_on"}
    elif "關燈" in text:
        return {"entity_id": "light.living_room", "service": "turn_off"}
    else:
        return {"error": "Command not recognized"}

if __name__ == '__main__':
    app.run(debug=True, port=5000)
```



4. 測試 API
使用工具(ex: Postman 或 cURL) 測試
    ```
    curl -X POST http://localhost:5000/api/command -H "Content-Type: application/json" -d "{\"text\":\"開燈\"}"
    ```

5. 整合到 Home Assistant
獲得的 YAML 需要被 Home Assistant 讀取才能執行。
所以要在 Home Assistant 的設定中，加入對應的腳本或自動化規則，才能根據 YAML 指令來控制 IOT。

---

### Docker

#### 使用 Docker 將本地檔案裝進容器內，讓 Home Assistant 在容器內讀檔

設定一個隔離的 Home Assistant 環境在 Docker 容器中運行，並使用本地的 YAML 檔案配置。
>  用 Docker 可以確保環境的一致性和安全，方便往後部署和維護。

#### 準備 Dockerfile

建一個 Dockerfile 用來指定如何建立容器，包括安裝 Home Assistant ，並設定容器，方便存取 YAML 檔案。
    (假設有一個檔名為 config.yaml 的 YAML 檔，就可以建立類似以下的 Dockerfile）
```yaml=
# 使用官方 Home Assistant 基礎映像
FROM homeassistant/home-assistant:latest

# 設定工作目錄
WORKDIR /config

# 軟連結來把本地檔案連接到 Docker 容器內
# 用軟連結來把本地檔案連接到 Docker 容器內（用 Docker 的 volume 功能）。
# volume 對本地檔案的任何更改會立即反映在容器內，對於開發和配置更新很方便

# 開放 Home Assistant 需要用到的 port
EXPOSE 8123

# 啟動 Home Assistant
CMD ["python", "-m", "homeassistant", "--config", "/config"]
```

*  建立 Docker 映像
    在 Dockerfile 所在目錄下，執行命令指令來建立 Docker 映像
    `docker build -t my-home-assistant .
`

#### 啟動 Docker 容器（方法一：Docker run)
建立映像後，用 `docker run -d --name my-ha-instance -p 8123:8123 my-home-assistant` 啟動一個 Docker 容器，它會運行 Home Assistant 並下載 YAML 檔案：
        
> 將容器內的 8123 port map到 host 的 8123 port，再去瀏覽 Home Assistant 的網頁。

        
#### 啟動 Docker 容器（方法二：Docker Compose）
用 Docker Compose 會使管理更加方便，而且可以很容易地擴展到更多服務，適用於多個容器和複雜配置。

舉例：設定檔、備份檔
![image](https://hackmd.io/_uploads/B1lbk6DM0.png)



運行例子：使用 docker-compose.yml 配置並運行 Home Assistant 容器，並使用本地的 config.yaml 作為配置

1. 創建 docker-compose.yml 

```yaml=
version: '3.8'
services:
  home-assistant:
    image: homeassistant/home-assistant:latest
    container_name: my-ha-instance
    volumes:
      - /path/to/your/config.yaml:/config/configuration.yaml
    ports:
      - "8123:8123"
    restart: unless-stopped
```

:::spoiler
各部分配置：
* version: 指定使用的 Docker Compose 文件版本。3.8 是目前支持的一個普遍版本。

* services: 定義要運行的各種服務(這裡用 home-assistant)

* image: 使用的 Docker 映像。（這裡用 Home Assistant 的最新官方映像）

* container_name: 容器的名稱。

* volumes: map 本地檔案到容器內的路徑。（確保將 /path/to/your/config.yaml 替換為本地 config.yaml 文件的實際路徑）

* ports: map 容器的 port 到 host 的 port(這裡將容器的 8123 port map 到 host 的同一 port，這是 Home Assistant 的預設 Web 界面 port)

* restart: 容器的重啟策略（unless-stopped ：除非手動停止，否則在退出時總是重啟）
:::



2. 啟動服務
在 docker-compose.yml 文件所在的目錄下，運行 `docker-compose up -d`

    如果第一次運行或者映像有更新，Docker Compose 會自動拉取最新的映像

3. 檢查運行情況
用 `docker-compose logs` 來查看容器的日誌(ex:系統運行正常)

或者用`docker ps`查看容器列表來確認 my-ha-instance 容器正在運行

#### 驗證設定
去 `http://localhost:8123` 看 Home Assistant 是否能正確使用 config.yaml 中的設定。
     

## 樹莓派
### 安裝
#### 樹莓派灌 HA OS
- 已經把 Home Assistant OS 安裝到 Pi4 上。
    - 安裝步驟：
        1. 將 Home Assistant OS 下載到 micro SD card
            - 安裝並啟動 Balena Etcher
            - 選擇 “Flash from URL”
            - 到 Home Assistant 官網說明文件複製 Home Assistant OS for  Raspberry Pi 映像檔 URL (看清楚自己樹莓派 Raspberry Pi 是哪個版本喔！)
        3. 插上 mirco SD card
        4. 插上網路線、Mini HDMI 並連到螢幕、鍵盤
        5. 插上電源
            - 到這一步會長這樣
                - ![image](https://hackmd.io/_uploads/HyVO81srA.png)
        6. 等待安裝 home assistant
        7. 在相同網路下，透過 home assistant 的 ip (會顯示在螢幕上) + port 8123 存取 web
            - ![image](https://hackmd.io/_uploads/r1ZK8JsSR.png)
            - ![image](https://hackmd.io/_uploads/HkF2LyiSC.png)

:::spoiler 無網路線時，樹梅派可以連 wifi(補充)

- 當沒有實體網路線，也可用 wifi
- https://community.home-assistant.io/t/changing-connectivity-to-wifi-via-cli/593450
:::

#### SSH Tunneling
- 目前有一台有在計中的 VM，其有一外部 IP。故此欲使用 SSH Remote Forwarding 讓我們能透過外部 IP 存取 Home Assistant web
    - 步驟
        1. 點左下方的帳號 -> 開啟進階模式
        1. 在 Home Assistant web 中安裝 ssh terminal : 設定 -> 附加元件 -> 右下角的附加元件商店 -> 安裝 `Terminal & SSH`
            - ![image](https://hackmd.io/_uploads/SJ8cmeiBR.png)
        2. 啟動此元件後，進入 web UI
        3. 設定計中 VM 的 SSH server，允許 connect to forwarded port
            - ![image](https://hackmd.io/_uploads/H1DKMgjHR.png)
            - `vi /etc/ssh/sshd_config`
            - add `GatewayPorts=clientspecified`
            - `sudo systemctl restart sshd`
         4. 將 SSH server 和 Home Assistant web 在背景做 SSH remote forward addressing
            - `ssh -fNR 0.0.0.0:8123:homeassistant.local:8123 {ssh_server_user_name}@{ssh_server_ip}` 
            > `ssh -fNR 0.0.0.0:8123:homeassistant.local:8123 tommygood@163.22.17.184`
         
         5. connect to ha web via public ip
            - ![image](https://hackmd.io/_uploads/rkuHBljS0.png)


## 智慧家電
[toc]

### 智慧插頭
#### 設定
1. 到 app store 下載 tapo 
2. 按下 plug 的 power
3. 到 tapo 新增設備
4. 打開藍芽找到對應的 plug
5. 更新 firmware of plug
6. 設定 plug 的 wifi
    - [wifi 必須是用 2.4 GHz 傳輸的 ](https://www.newmobilelife.com/2022/10/31/how-to-set-your-iphone-hotspot-to-2-4-ghz/)
    - 如果是用 apple mobile 熱點，要去個人熱點打開最高相容性
7. 在 tapo 上測試看看可不可以開關 plug
8. 在 tapo 上看該 plug 的 ip
9. 到 HA 的 設定 -> 裝置與服務 -> 新增整合 -> search TP-Link -> 新增 TP-Link Smart Home -> 輸入 plug ip -> 輸入 TP-cloud（tapo）的 email 帳號與密碼作為驗證
10. 在 HA 測試開關 plug
    ![image](https://hackmd.io/_uploads/S1YC117xC.png)

- ref
    - https://www.home-assistant.io/integrations/tplink

### 新增裝置到 HA
#### 不同安裝方式可以使用的功能
若以 container 方式(docker)無法使用 add-ons，因各個 add-ons 是以 container 方式安裝，所以建議直接在樹梅派上裝 Home Assistant OS

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


### 門窗感應器

問題:
- 可以在米家 app 上偵測並正確回傳資料，但若用 ha 的 xiaomi ble 就會無法偵測到裝置
- sol:
    - 先用藍芽追蹤的套件看一下 log
    -  但發現沒有該bluetooth_tracker all-ons
    - https://www.home-assistant.io/integrations/bluetooth_tracker/

#### 連不上藍芽
[用小米BLE套件](https://home-assistant.io/integrations/xiaomi_ble/)

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

#### 為何連不上
[藍芽intel NUC問題](https://community.home-assistant.io/t/problems-with-bluetooth/623061
)
![image](https://hackmd.io/_uploads/HylFoLXV0.png)

#### API
[官方介紹](https://developers.home-assistant.io/docs/api/rest/)
![image](https://hackmd.io/_uploads/SyYCEv4EA.png)

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


### scan device
- tuya/smart-life : https://github.com/tuya/tuya-home-android-sdk-sample-java
- tapo/tp-link : https://github.com/petretiandrea/home-assistant-tapo-p100?tab=readme-ov-file
- matter : https://github.com/google-home/sample-apps-for-matter-android/tree/main
##### install
- gradle : https://www.geeksforgeeks.org/how-to-install-gradle-on-windows/
- android studio : https://developer.android.com/studio?hl=zh-tw



## 新增裝置(Tapo)

### Use Case

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


### 手動新增裝置
原本新增畫面如下:

- 先用 tapo app 登入自己的帳號，並把 tapo 裝置掃描進 app
- 開啟 Homeassistant web，前往點擊"設定"->"整合與服務"->"TP-Link Tapo"->"新增裝置"
    - ![image](https://hackmd.io/_uploads/SkxrIfVaR.png)

- 如果點擊"新增裝置"，會跳出輸入框
    - 需輸入待新增的 device ip、tapo app 的帳號、密碼
    - ![image](https://hackmd.io/_uploads/r1yy1V4pR.png)

- 新增成功
    - ![image](https://hackmd.io/_uploads/HJEbDzNp0.png)


### API 新增裝置
- 先用 tapo app 登入自己的帳號，並把 tapo 裝置掃描進 app，由 tapo app 分配一組 ip 該裝置。
- 讓樹莓派掃描附近的 tapo device（必須已經有用 tapo app 設定 ip）
    - `tapo_discover.py`
        -  ![image](https://hackmd.io/_uploads/BJ_CPfET0.png)

- add tapo device with device ip
    - `tapo_add_device.py`
    - API 會把 device ip、tapo app的帳密、以及flow id 傳給 HA，就能新增成功裝置
        - ![image](https://hackmd.io/_uploads/HJ7NvGVp0.png)

> 省略掉打開web，點擊"設定"->"整合與服務"->"TP-Link Tapo"->"新增裝置"，
> 再手動輸入 『device ip、tapo app的帳號、密碼』的步驟
   
   - 可以在 [http://163.22.17.184:8123/config/integrations/integration/tapo](http://163.22.17.184:8123/config/integrations/integration/tapo) 看到新增的裝置
        - ![image](https://hackmd.io/_uploads/rJUuwMETA.png)

#### Code
##### tapo_discover.py

- [ref](https://github.com/petretiandrea/plugp100)

Overview:
1. Creates credentials (using the username and password)
2. 掃描網路中的 Tapo 設備，用提供的憑證更新設備資訊。如果成功，print 設備的 type,protocol version,and raw state。


#### tapo_add_device.py

Overview:
1. Sends a request to Home Assistant to get a flow_id
2. 用 flow_id 將設備資訊（device’s IP、username、pwd）傳給 HA，完成設備新增。
    > flow_id
    > ![image](https://hackmd.io/_uploads/BJjgl446R.png)
    > ![image](https://hackmd.io/_uploads/Hy8Qe4E6C.png)


## 掃描 Tapo 裝置 
1. ping 同個網路下所有 ip，並找出可用的，再一一用 tapo 去連接
2. 連接成功會回傳 device info

- 程式碼會掃描區域網路中每個可能的 IP 地址，看看哪一個是可以回應 ping 的。
- 因為掃描 254 個 IP 會很慢，所以程式用了多執行緒（ThreadPoolExecutor），同時掃描很多 IP 地址。這樣可以並行 run 多個 ping 操作，同時檢查多個 IP 是否有回應，加快掃描。

- 當掃描找到「活躍」的 IP 後，程式會嘗試去連接這些 IP，確認它們是否是 Tapo 裝置(這裡用到async，可以同時處理多個查詢，減少等待時間）。
    > 如果程式直接用同步方法，遇到需要等待的時候就會卡住，但異步處理可以在一個裝置還沒回應時，先去連接其他裝置，來提升效率。

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



## 建 Database
- 已建好 DB
![image](https://hackmd.io/_uploads/Hko3ZRsJJg.png)


- 之後會提供 可新增、刪除、修改、查詢的 API 讓大家用。
> 前端登入後，後端會存 session，然後 call API 會驗證 session


## 分區裝置 API 、 solve CORS problem

### 前端有 CORS 的問題

#### CORS 原理
瀏覽器收到 http 的 response 時，policy 的 header 會有 CORS 的 policy，policy 要符合規則，瀏覽器才會把回傳的 responses 讓 javascript 繼續執行。
> 我嘗試在 HA web 那邊設定檔要加上 policy 結果發現無法做到。因為 HA 系統不完善，沒辦法改這部份設定檔，網路上大家也都遇到這問題，可以說是無解。

#### 解決辦法
所以就從 nginx 去做 reverse proxy，目前是在 proxy 的地方幫 response（http 的 header） 加上 CORS 的 policy。
> 但以後要 call HA web 自己的 API 時（ex:聊天機器人之類的），就要改用 8124 port 才能 work。


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

---

### 使用資料庫: 裝置分區 API
- 可修改裝置名稱和裝置分區
![image](https://hackmd.io/_uploads/HJ7cicOb1e.png)



## 註冊 APP 帳號、刪除裝置
### 註冊帳號
- call http://163.22.17.116:8122/api/account 並帶入想註冊的帳密
- post 該帳密到 http://163.22.17.116:8122/api/login 即可登入成功
![image](https://hackmd.io/_uploads/BJcA5WjM1l.png)



### 刪除裝置

- call http://163.22.17.116:8122/api/device 可以拿到目前所有的設備
![image](https://hackmd.io/_uploads/Sk2Os-jzJg.png)
![image](https://hackmd.io/_uploads/ryNI1fszyx.png)

- 找到想刪除之裝置的 entry_id (ex:01JD45BQBMK9DWE55CFSMRW1M6)
    > entry_id 在重新新增裝置進 HA 時都會變動

- 把該 entry_id 帶入 delete 的 method 就能刪除裝置 
    - ![image](https://hackmd.io/_uploads/SJbm1fjGkg.png)

    - ![image](https://hackmd.io/_uploads/SyX--zsfJg.png)
        ```
        >>> data={'dev_id' : '01JD45BQBMK9DWE55CFSMRW1M6'}
        >>> a=requests.delete('http://163.22.17.116:8122/api/device', json=data)
        ```

- 刪除成功
    - ![image](https://hackmd.io/_uploads/Skce-GjGJl.png)

## 掃描、新增裝置 API
- 查看樹莓派附近掃描到的所有 Tapo 裝置 API（掃描會需要等個 5~10秒）
    - 會掃描到所有`已新增`和`未新增`的 Tapo 裝置
    - GET http://163.22.17.116:8122/api/device/discovery
    - ![image](https://hackmd.io/_uploads/S1YSrcTMJl.png)
- 新增裝置 API
    - 先用上述掃描 API 抓到裝置的 'dev_ip' 
    - 再 POST http://163.22.17.116:8122/api/device 就能直接新增裝置了
        - data
            ```js=
                data = {
                    'dev_ip' : device ip
                }
            ```
        - test
        ```python=
        class Device :
            def __init__(self) :
                pass

            def discovery(self):
                BASE_URL = "http://163.22.17.116:8122/api/device/discovery"
                response = requests.get(BASE_URL)
                if response.status_code == 200:
                    print("搜尋裝置成功", response.json())
                    self.insert(list(response.json()[0].keys())[0]) # 新增第一個裝置做為測試
                else:
                    print("搜尋裝置失敗:", response.status_code)

            def insert(self, dev_ip):
                print('ip', dev_ip)
                BASE_URL = "http://163.22.17.116:8122/api/device"
                data = {
                    "dev_ip" : dev_ip
                }
                response = requests.post(BASE_URL, json=data)
                if response.status_code == 201:
                    print("新增裝置成功", response.json())
                else:
                    print("新增裝置失敗:", response.status_code)
        ```
## Graph
### 系統架構圖
- ![image](https://hackmd.io/_uploads/rk5SHHyXkg.png)
- 這是我們的系統架構圖，前端的應用程式採用 vue.js 開發，並且藉由呼叫後端的 restful api 來將資料動態渲染在頁面上
- 在我們的 web server 和 home assistant web 前面，我們藉由在一台有 public ip 的 ubuntu server 上安裝 nginx 作為反向代理的伺服器，這樣可以達成減少 public ip 的使用，以及未來可以視系統的使用負載情況調整是否要做 load balance 等等。同時，我們在此台 ubuntu 上也安裝 MySQL 作為我們系統的資料庫。
- 我們也藉由架設 Access Point，讓我們的樹莓派和 IoT 裝置能連到相同的 class C 區域網路以做溝通，另外我們在樹莓派上安裝了 Home Assistant OS 作為它的作業系統，以及基於此上再去架設 web server，以利用 home assistant web 的 restful api 和系統中安裝的網路工具去提供管理 IoT 裝置的功能並且提供 restful api 供前端呼叫讓使用者使用
- 最後則是 home assistant 和 chatgpt 的串接，這是藉由在 home assistant 設定我們的 openai 的帳號，讓 chatgpt 能存取到我們 home assistant 的內部資訊，讓使用者和 chatgpt 溝通時，對話能夠流暢。

### 流程圖
- 登入 & 註冊
    - ![image](https://hackmd.io/_uploads/B1KNcrkm1x.png)
- 新增裝置
    - ![image](https://hackmd.io/_uploads/rJfH5S17Jg.png)
- 聊天機器人
    - ![image](https://hackmd.io/_uploads/r12qkTQmkl.png)

### 說明
- Vue.js
    - 前端 application
- Linux Server
    - 和計中借用一台有對外 ip 的 Ubuntu 22.04，主要提供以下兩個功能
    1. 反向代理：採用 Nginx 做 reverse proxy 到 Pi 上面的 nodejs server & home assistant web，讓前端網頁可以存取到 api
    2. 資料庫：採用 MySQL，存放含帳號密碼等資料
- Access Point
    - 將樹梅派和 IoT devices 部屬於同一個 ap，讓它們在同個區網內以便於互相溝通
- IoT Devices
    - 包含多個品牌的多種 IoT Device
- NodeJS
    - 和資料庫 & Home Assistant 的 RestFul API 串接，並提供 RestFul API 讓前端 application 可以使用提供的功能（如：註冊登入、新增裝置）
    - 利用 `iputils` 提供的工具（目前使用 `ping`）搜尋區網內的裝置及其資訊，詳細<a href='#如何和-tapo-溝通'>可看上面</a>
- Home Assistant
    - 利用官方、非官方的 IoT Integrations 管理不同品牌的 IoT devices
    - 提供 RestFul API，NodeJS 利用此以提供一些進階的功能（如：分析資源使用率）
    - smart assitant & ChatGPT：請補充

## 如何和 IoT 溝通
- 接下來，我們要介紹我們系統的其中一個特色，和多品牌的 IoT 裝置溝通，包含新增和控制裝置，讓使用者可以不用去下載和操作多個品牌的 app
- 要達成此目的，我們必須先去觀察該品牌的 app 是如何和 IoT 裝置溝通的
- 我們主要以 tapo app 作為範例
### 觀察的方法
- packet sniffing
    - 攔截 tapo app 和 device 溝通的封包並觀察內容
- reverse engineering
    - 將 tapo app 的 `.apk` 反編譯成 java source code
### tapo app
- tapo 中，有部分裝置（比較舊版的韌體）會使用 http 和 app 溝通，因此我們可以觀察封包的內容，並嘗試利用我們建立的 http request 而不是 tapo app 和裝置溝通
1. handshake request : POST `http://<device-ip>/app`
    - body
        ```java=
        {
          "method": "handshake",
          "params": {
            "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiHkY5laTugGN1Hf/sBHiiw6mnnkohmvVHHHGJqwRx59RjQaL/SPBoLpeNRgN3B/uykzYTLUVMpTcWSZHsS6FfhdoOkJ1B6nit6nheIfltbP99uJduP1JQ44S9dqUr73w++Lpl6TKrzK3KOc5z/vc9xmqiKK6PYbFZu2evCsL19wIDAQAB-----END PUBLIC KEY-----\n"
          },
          "requestTimeMils": 0
        }
        ```
    - 實作 key 的 java source code : 利用 RSA 產生非對稱式加密的 public/private key，再利用 base64 編碼後傳輸
        ```java=
        public void mo35029c() {
            KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
            instance.initialize(1024, new SecureRandom());
            KeyPair generateKeyPair = instance.generateKeyPair();
            String str = new String(Base64.encode(((RSAPublicKey) generateKeyPair.getPublic()).getEncoded(), 0));
            String str2 = new String(Base64.encode(((RSAPrivateKey) generateKeyPair.getPrivate()).getEncoded(), 0));
            this.f20965b.put(0, str);
            this.f20965b.put(1, str2);
        }
        ```
        - 之後的 data request 的 body 會用此金鑰做非對稱式加密
2. authentication request
    - 將 tapo app 的帳號密碼給該 device，device 會再去確認是否可以登入
    - request body
        ```java=
        {
          "method": "login_device",
          "params": {
            "password": "ITcyNjU....",
            "username": "MzhhNTk2NT..."
          },
          "requestTimeMils": 0
        }
        ```
4. data request
    - 可以開始傳輸資料做 device 設定
    - 將 request body 用私鑰解密後，可以得到以下的內容
        ```=
        {
          "method": "set_device_info",
          "params": {
            "device_on": false
          },
          "requestTimeMils": 1602840338865,
          "terminalUUID": "88-54-DE-AD-52-E1"
        }
        ```
### plugp100
- `plugp100` : 目前此 custom component 實作了 tapo 部分裝置的搜尋和控制（新增裝置到 HA, 更改裝置狀態）的功能
- 搜尋問題
    1. 有時候會找不到全部的 device：後來發現因此 component 使用 udp broadcast 來尋找 device，但是在指定的 timeout 時間內，udp socket 不一定會接受到所有 device 的 response
    2. 找到無法控制的 device ：因為新出的 tapo device 的新版 firmware 會使用不同 protocol（從 http 變 https）和 tapo app 溝通，但是此情況下 plugp100 仍會顯示新出的 tapo device，但該 tapo device 無法用 plugp100 目前提供的控制方式（http）讓該 device 連接到 home assistant
- 解決方法
    - 利用 `ping` 和 plugp100 提供的控制方式寫一個偵測和新增 tapo device 的功能解決上述兩個問題
    - 並且利用 multithreading 加速尋找的效能
### ref
- https://k4czp3r.xyz/blog/post/reverse-engineering-tp-link-tapo
- https://github.com/petretiandrea/plugp100/blob/main/plugp100/discovery/tapo_discovery.py
