從零開始的 IoT 系統設計
Toby
Toby

說到 IoT (物聯網)很多人第一時間就會想起 Google Home ,Apple HomeKit,Amazon Echo 之類的智能喇叭(音箱),但是他們並不是真的跟 IoT 有關,他們比較像是一個輸入裝置或是一個 gateway 去控制其他裝置而已。

另外比較有技術背景的人可能會想起一些 IoT 的技術,好像說 Bluetooth BLE,Zigbee,2.4Ghz RF 跟 IR 之類的傳輸技術,也有另外一些人會想起 IFTTT、MQTT 之類的通訊協議。可是我們這次不是要說這個,而是

怎樣從零開始自己幹一套 IoT 控制系統出來

嘛,雖然說是從零開始但是總不能從沙開始煉硅晶吧?所以這裡的零界定為:

不使用任何專門為 IoT 設計的產品或軟件來制作這套 IoT 系統。

托比

因為之前我在高中畢業到大一之間的暑假我也曾經研究過一套叫 Home Dynamic 的 IoT 系統,所以這套東西就沿用這個名字叫做 Home Dynamic v2 吧!

選擇硬體平台

如果要開發物聯網系統,最基本的話就是需要有「物聯網」中「物」的部分吧?所以我就先由硬體開始選擇。這裡我有幾個要求

  1. 便宜,而且容易買到
  2. 體積要夠小,而且輕便
  3. 省電(因為要 24/7 的運作)

所以這裡就直接省卻了基於 ARM 處理器的開發板了。剩下的就只有 Arduino 系列用的 ATmega 或是更省電的 ATtiny 或者 ESP 系列的 ESP8266 或 ESP32

選擇連接協議

到了「物聯網」中「聯」的部分,到底要怎樣讓這塊板子連接到其他裝置上?市面上有很多不同的 IoT 連接協議及相關的硬體,常見的包括:

  • Zigbee (通常用在 Mesh 網絡上 )
  • Bluetooth BLE (通常用在需要超省電的裝置上)
  • LoRa (通常用作長距離通訊)
  • 2.4Ghz RF (通常用作即時控制,如搖控車)
  • WiFi (對,就是你手機筆電在用的那個 WiFi)

這裡的選擇其他也滿簡單的。首先,LoRa 一定用不上,香港的房子連轉身的空間也沒有,能連接數公里距離的 LoRa 一點用途都沒有。 Zigbee 的確是其中一個可以考慮的選擇,但是因為我也想透過現有的 ArozOS 系統控制它,而 Android、Windows 等作業系統都沒有原生支援 Zigbee,所以在這裡也不作考慮。

最後只剩下 3個選擇:BLE,2.4Ghz RF 跟 WiFi ,不過反正大家都是 2.4Ghz,而且因為我的 IoT 裝置都是用來控制電源、或是用來檢測太陽能板等很容易都抓到電源的地方,那當然選擇最容易可以透過不同手提裝置控制的選擇:WiFi

選擇網絡連接方式

說到「物聯網」的「網(絡)」,既然都跑 WiFi 了當然是用 HTTP 啊!雖然也有其他連接方式例如 MQTT 之類的,但是在選擇這個技術之前要考慮到幾點

  1. 這種技術會不會有 SPOF (Single Point of Failure)
  2. 這種技術會不會需要額外的伺服器 / 硬體
  3. 這種技術開發起來方不方便(畢竟是寫給自己用,賺不了錢所以太複雜了反而沒意思)

所以就用最簡單的 GET request 就好了啦。而且這方法也不需要 ESP32 內置的藍牙硬體,所以我們可以直接選擇最便宜的 ESP01 來做我們的控制器,一石二鳥。

可是,我們怎樣從網絡中找到 IoT 裝置?

這是一個好問題,而且之前我在開發 Home Dynamic 系統的時候也遇過類似的問題。但是作為剛剛升大學的我,當時我使用了一個最原始的方法:每個 IP ping 一次

但是在這次的開發計劃中(我把它叫做 Home Dynamic v2),我打算使用 mDNS 來取代這個(爛)IP 掃描方法。畢竟比起一次過把整個區網 ping 一次,例不如讓裝置自己 broadcast 自己的信息好了。剛好 ArozOS 也有內置到 mDNS 掃描器,所以就直接拿來做 IoT 掃描器不就行了嗎?

於是,我把 mDNS 的 transponder 塞到 ESP8266 裡面,然後跟據 ArozOS 的 mDNS transponder 結構寫了個這樣的 metadata 到 transponder 的回應信息裡面。以下為 hdsv2 的電源開關控制器的代碼:

//Inject zeroconf attr into the MDNS respond (For scanning by ArozOS)
void MDNSDynamicServiceTxtCallback(const MDNSResponder::hMDNSService p_hService) {
    //Define the domain of the HDSv2 devices
    MDNS.addDynamicServiceTxt(p_hService, "domain","hds.arozos.com");
    MDNS.addDynamicServiceTxt(p_hService, "protocol","hdsv2");

    //Define the OEM written values
    MDNS.addDynamicServiceTxt(p_hService, "uuid",getMacAddress());
    MDNS.addDynamicServiceTxt(p_hService, "model","Test Unit");
    MDNS.addDynamicServiceTxt(p_hService, "vendor","HomeDynamic Project");
    MDNS.addDynamicServiceTxt(p_hService, "version_minor","0.00");
    MDNS.addDynamicServiceTxt(p_hService, "version_build","0");
}

然後在 ArozOS 啟動 console 模式,並在 terminal 中輸入

scan all

便得到了區網內的 mDNS 回傳

嗨!找到新的 WiFi IoT 裝置了

這個時候我們就有辦法找到 IoT 裝置的 IP 地址了。

安全性問題?不會被人入侵嗎?

這也是一個好問題,但是作為開發者,我自己寫的 IoT 裝置是一定不會讓它連到外網的。所以這樣想的話,既然都是把所有東西放在 NAT 路由器後面,而且家裡都是認識的人,應該不用甚麼密碼才能控制裝置的設計吧?如果你還是不放心的話就再加一個 IoT 專用的 NAT WiFi 路由器就好了啦

噢,你問怎樣在家外面控制家裡面的 IoT 裝置?這就要說到我準備要解釋的 IoT Gateway – ArozOS 的部分了。

與 ArozOS 連接及產生控制界面

甚麼是 ArozOS

ArozOS 是一套也是由我開發的雲端桌面系統,可以架設在 Raspberry Pi 上並透過 uPnP 穿透到外網,讓我自己在外面都可以透過網頁桌面存取到我放在 Rpi 上的文件。

網頁桌面界面大約長這樣

這東西也是開源的,可以在這裡找到:

https://github.com/tobychui/arozos

ArozOS 上的 hdsv2 控制器

ArozOS 上面有一個叫 IoT Hub 的綜合控制器,可以讓開發者把自己寫的 IoT 系統與 ArozOS 的 IoT Hub 控制界面連接。它的 Go Struct 看上去像這樣:

type ProtocolHandler interface {
	Start() error                                                                         //Run Startup check. This IoT Protocl Handler will not load if this return any error (e.g. required wireless hardware not found) **TRY NOT TO USE BLOCKING LOGIC HERE**
	Scan() ([]*Device, error)                                                             //Scan the nearby devices                                                        //Return the previous scanned list
	Connect(device *Device, authInfo *AuthInfo) error                                     //Connect to the device
	Status(device *Device) (map[string]interface{}, error)                                //Get status of the IoT device
	Execute(device *Device, endpoint *Endpoint, payload interface{}) (interface{}, error) //Execute an endpoint for a device
	Disconnect(device *Device) error                                                      //Disconnect from a device connection
	Stats() Stats                                                                         //Return the properties and status of the Protocol Handler
	Icon(device *Device) string                                                           //Get the icon of the device, see iot/hub/img/devices for a list of icons
}

只要我們把裡面要求的 function 都寫出來它就會接受這個新模組為新的 IoT 掃描器了。於是我便跟著它的架構寫了一個 hdsv2 的控制器。完整代碼可以在下面找到 (在 hdsv2 資料夾裡面):

https://github.com/tobychui/arozos/tree/master/src/mod/iot

然後順便在 ArozOS 核心中(iot.go)加入一行以登錄這個新的控制器

https://github.com/tobychui/arozos/blob/master/src/iot.go

//Home Dynamic v2
hdsv2Handler := hdsv2.NewProtocolHandler(MDNS)
iotManager.RegisterHandler(hdsv2Handler)

這樣 ArozOS 就能使用這個控制器來掃描跟控制這套新的 IoT 系統了。

ESP8266 上的資訊接點

然後為了讓 ESP8266 可以告訴我寫的控制器這個裝置是幹麼的,到底怎樣才能控制到它,所以我特別設計了幾個 endpoint 並讓伺服器可以要求這些節點以了解系統應該怎樣控制那個裝置。以下為一個很簡單開關的 endpoint respond (在 C++ 裡寫 JSON 真的很麻煩啊)

void handle_endpoints() {
  server.send(200, "application/json", "[{\
  \"Name\": \"ON\",\
  \"RelPath\":\"on\",\
  \"Desc\":\"Switch on the device attached to the switch\",\
  \"Type\":\"none\",\
  \"AllowRead\":false,\
  \"AllowWrite\":true\
},{\
  \"Name\": \"OFF\",\
  \"RelPath\":\"off\",\
  \"Desc\":\"Switch off the device attached to the switch\",\
  \"Type\":\"none\",\
  \"AllowRead\":false,\
  \"AllowWrite\":true\
}\
]"); 
}

由於這部分實在太技術性了所以我就直接把它放到 Github,你們有興趣可以看這裡:

https://github.com/tobychui/Home-Dynamic/tree/master/Home%20Dynamic%20v2

簡單來說就是它會讓掃描器存取它的狀態(status)跟接入點(endpoints),然後讓 ArozOS 前端去產生一個對應的 UI 給使用者控制。

運作原理

而最後這系統的運作原理簡化之後大約是這樣

中文版本
英文版本

完成~

系統完成之後大約長這樣子

我們的 HDSv2 Test Unit 被 ArozOS 的 IoT Hub 掃描出來了
控制界面,可以輸入各種數值

源碼與架設教學

可以在這裡找到

ArozOS 雲端桌面系統(安裝到 Raspberry Pi 上當 IoT 控制器)

https://github.com/tobychui/arozos

Home Dynamic v2 的源碼及 Arduino IDE 例子

https://github.com/tobychui/Home-Dynamic