# 畢業專題 - 智慧家居系統 支援多品牌家電整合 [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