# 畢業專題 - 智慧家居系統 支援多品牌家電整合
[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,不需重新建立連線
- 支援多種語言的實作
- 可以有帳號密碼機制
- 
- 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 會使管理更加方便,而且可以很容易地擴展到更多服務,適用於多個容器和複雜配置。
舉例:設定檔、備份檔

運行例子:使用 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. 插上電源
- 到這一步會長這樣
- 
6. 等待安裝 home assistant
7. 在相同網路下,透過 home assistant 的 ip (會顯示在螢幕上) + port 8123 存取 web
- 
- 
:::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`
- 
2. 啟動此元件後,進入 web UI
3. 設定計中 VM 的 SSH server,允許 connect to forwarded port
- 
- `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
- 
## 智慧家電
[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

- ref
- https://www.home-assistant.io/integrations/tplink
### 新增裝置到 HA
#### 不同安裝方式可以使用的功能
若以 container 方式(docker)無法使用 add-ons,因各個 add-ons 是以 container 方式安裝,所以建議直接在樹梅派上裝 Home Assistant OS

### 門窗感應器
問題:
- 可以在米家 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/)

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

#### API
[官方介紹](https://developers.home-assistant.io/docs/api/rest/)


### 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

### 手動新增裝置
原本新增畫面如下:
- 先用 tapo app 登入自己的帳號,並把 tapo 裝置掃描進 app
- 開啟 Homeassistant web,前往點擊"設定"->"整合與服務"->"TP-Link Tapo"->"新增裝置"
- 
- 如果點擊"新增裝置",會跳出輸入框
- 需輸入待新增的 device ip、tapo app 的帳號、密碼
- 
- 新增成功
- 
### API 新增裝置
- 先用 tapo app 登入自己的帳號,並把 tapo 裝置掃描進 app,由 tapo app 分配一組 ip 該裝置。
- 讓樹莓派掃描附近的 tapo device(必須已經有用 tapo app 設定 ip)
- `tapo_discover.py`
- 
- add tapo device with device ip
- `tapo_add_device.py`
- API 會把 device ip、tapo app的帳密、以及flow id 傳給 HA,就能新增成功裝置
- 
> 省略掉打開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) 看到新增的裝置
- 
#### 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
> 
> 
## 掃描 Tapo 裝置
1. ping 同個網路下所有 ip,並找出可用的,再一一用 tapo 去連接
2. 連接成功會回傳 device info
- 程式碼會掃描區域網路中每個可能的 IP 地址,看看哪一個是可以回應 ping 的。
- 因為掃描 254 個 IP 會很慢,所以程式用了多執行緒(ThreadPoolExecutor),同時掃描很多 IP 地址。這樣可以並行 run 多個 ping 操作,同時檢查多個 IP 是否有回應,加快掃描。
- 當掃描找到「活躍」的 IP 後,程式會嘗試去連接這些 IP,確認它們是否是 Tapo 裝置(這裡用到async,可以同時處理多個查詢,減少等待時間)。
> 如果程式直接用同步方法,遇到需要等待的時候就會卡住,但異步處理可以在一個裝置還沒回應時,先去連接其他裝置,來提升效率。

## 建 Database
- 已建好 DB

- 之後會提供 可新增、刪除、修改、查詢的 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。

---
### 使用資料庫: 裝置分區 API
- 可修改裝置名稱和裝置分區

## 註冊 APP 帳號、刪除裝置
### 註冊帳號
- call http://163.22.17.116:8122/api/account 並帶入想註冊的帳密
- post 該帳密到 http://163.22.17.116:8122/api/login 即可登入成功

### 刪除裝置
- call http://163.22.17.116:8122/api/device 可以拿到目前所有的設備


- 找到想刪除之裝置的 entry_id (ex:01JD45BQBMK9DWE55CFSMRW1M6)
> entry_id 在重新新增裝置進 HA 時都會變動
- 把該 entry_id 帶入 delete 的 method 就能刪除裝置
- 
- 
```
>>> data={'dev_id' : '01JD45BQBMK9DWE55CFSMRW1M6'}
>>> a=requests.delete('http://163.22.17.116:8122/api/device', json=data)
```
- 刪除成功
- 
## 掃描、新增裝置 API
- 查看樹莓派附近掃描到的所有 Tapo 裝置 API(掃描會需要等個 5~10秒)
- 會掃描到所有`已新增`和`未新增`的 Tapo 裝置
- GET http://163.22.17.116:8122/api/device/discovery
- 
- 新增裝置 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
### 系統架構圖
- 
- 這是我們的系統架構圖,前端的應用程式採用 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 溝通時,對話能夠流暢。
### 流程圖
- 登入 & 註冊
- 
- 新增裝置
- 
- 聊天機器人
- 
### 說明
- 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