2017年2月7日 星期二

Elastic Network of Things with MQTT and MicroPython

Wei Lin
20170207
圖片來源


摘要

 IoT 系統的末端節點通常是由小型的嵌入式設備來擔任,當節點數量較多時,之間資訊交換的複雜性也就跟著提高,而系統在佈署或改版調整時,更加需要一個可以由遠端主機動態規劃、佈署、管理與控制的機制,才能 具有省時省力、動態與彈性的效果。
 
 現今 IoT 的主流 frameworks,有許多都是基於 MQTT 的協定,屬於一種在 broker-client 架構上的 publisher / subscriber 機制,雖然推拉訊息的方式相當具有彈性,然而對訊息的操作僅有 publish 和 subscribe 兩種方法,普遍缺少 RPC 的功能,在撰寫程式與建構系統的時候會受到某種程度的限制。

 然而,有很多優秀的 message-queue frameworks,例如 CeleryIPython Parallel,有著類似的broker-client 架構與 producer / consumer 機制,並且具有 RPC 的功能,造就了強大的 平行/分散式運算的環境。

 本實驗中基於類似 IPython Parallel 的架構,運用幾顆運行 MicroPythonESP8266 模組,將之視為 Worker(Engine)來建構一個小型系統,並且受惠於 Python 的特性,可以動態地傳送 任意的程式碼 要求遠端端點執行,因此端點上的運行邏輯隨時可變,並不受限於預設的程式碼。
 
 本系統採用 Mosquitto 作為 broker,遵循  MQTT 協定,各節點透過 publisher / subscriber 的方式來傳遞訊息時具有相當的彈性與效率。除此之外,本系統還提供了 RPC 的機制,在建構系統的程式方法上有更大的自由度。

 基於上述的機制,我們可以經由中央主機讀寫各遠端節點上的GPIO來進行動態控制,也可以透過網路佈署程式碼交由 各節點獨立運行,各節點  (worker) 雖然很小,但其之間也可以互相溝通,共同建構一個自主、動態的 IoT 系統。  

 


 

說明

 Arduino 的出現掀起了一股 Maker 的風潮,它的成功歸功於很多因素,很重要的一點是因為 Arduino 具有相當的方便性,開發模式與環境都相當容易上手,個人覺得具有下列的特色:


Arduino 運作模式的優缺點:

 使用 Arduino 的時候,都會將寫好的程式燒錄到 Arduino 開發板子上,然後 Arudino 即可獨立運作,必要的時候透過 Serial、Bluebooth、WiFi 與外界溝通傳輸資料。 這種模式的好處是:
  • 獨立性高而且相當的穩定,
 但相對的 缺點是:
  • 程式改版很麻煩,需要將機器下線,燒錄新版的程式,再重新上線。
  • 無法容納複雜的邏輯,因為 Arudino 上的記憶體空間有限。
 一方面因為需要容納更複雜的邏輯,另一方面如果機器的數量很多,地理位置分散,更新程式將是費時費力的工作,因而 有了 Firmata 這種混合式的作法。


Firmata 的優缺點:

 Firmata 基本上就是把 Arduino 當作一個 interpreter,接收主機透過 Serial 介面傳來的指令,解譯之後處理之。而主機上有一個對應的代表Arduino的軟體物件,主機與代表Arduino的軟體物件的互動,都將會透過 Serial介面傳遞並指揮遠端的 Arduino實體硬體做對應的動作 (範 例)。
這種模式之下的優點是:
  • 遠端主機可以藉由連線 讀寫硬體機器上的 GPIOs,並做對應的處理與控制,邏 輯上可以更複雜與彈性,遇到邏輯需要變更的時候 能保有相當程度的彈性與方便性。
  • Arduino除了接收並執行遠端主機傳過來的指令之外,也可以以 loop迴圈同時執行固定的工作,具 有某種程度的獨立性
而相對地 缺點是:
  • 如果需要主機讀寫GPIOs並加以控制,則會增加主機的負擔
  • 如果 Arudino 中的 loop 迴圈程式的邏輯需要調整,還是必須要經過 下線、燒錄新版程式、重新上線的過程,仍然逃不過改版費時費力的 宿命。



  在 IoT 很熱門的今天,一個系統已經不是一兩台設備的規模而已,設備的數量會比較多,除了上述的 系統佈署、程式改版的彈性與方便性之外,更需要有一個高效的資訊溝通機制,也因此有了適合 IoT 設備的 MQTT 通訊協定。




MQTT 的角色與特性:

 目前有許多 IoT 的 frameworks,都是基於 MQTT 的協定,基本上是一種在 broker-client 架構上的 publisher / subscriber 機制,其中設立了很多 "Topic",各節點透過定義好的 topics 來溝通。可惜大部分的 MQTT implementations 都沒有提供 RPC 的機制。如果可以具有 RPC 的功能,我覺得會大大地提升 IoT 系統的威力。

Swarm 圖片來源


 物聯網裡面的物件當然要有聯網的能力,近兩年 ESP8266 因為具有 WiFi 的聯網能力,價格也相當便宜,因此在 Maker 界相當的受到歡迎與愛用,而且它可以獨立運作,不一定要搭配 Arduino 。



ESP8266 可以獨立運作:

  ESP8266 是一款相當受到歡迎的WiFi晶片,衍生的模組與可以找到的資 源相當多。
其實 ESP8266 並不是必須搭配 Arduino 才能工作,它裡面其實也有一個 MCU,而且具有自己的 GPIOs、ADC、Serial... 腳位。 有的 ESP8266 模組具有 512KB ~ 4MB 或以上的 Flash,因此,我們可以將程式碼直接燒錄在 ESP8266 模組上面,把它當成一個獨立的個體,用在各種適合的用途上,也就是說,可以把它當成一個具有 WiFi能力的 Arduino 來用

 既然 ESP8266模組可以獨立運作,又可以連上網路,我們可以把它視為可獨立使用的 最小的 IoT 單元。然而,是否還是只能燒錄一些 MQTT 或者其他協定的資料收發程式來使用呢? 有沒有更有彈性與威力的作法?

 

IPython Parallel:

 在上述關於 MQTT 的介紹中我們可以看到,系統是以一個 MQTT broker 為中心,各節點透過 broker 來交換訊息。這種架構因為很優秀,所以也常常可見。MQTT 和 之前淺略接觸過的 IPython Parallel 在架構上都有相似之處。

圖 片來源
 
   
 IPython Parallel 最讓我印象深刻的,也是和 Celery 很大的一個差異點,就是透過 IPython Parallel 可以動態地派送任意的程式碼給遠端的 engine (worker) 去執行,程式碼不用先存放一份在遠端節點上,這讓 IPython Parallel 顯得比較彈性多了。另外不管是 IPython Parallel 或者 Celery,都提供 RPC 的機制,讓建構於其上的系統更有效能。

 能與遠端節點做 RPC 的互動,而且能動態的佈署程式與邏輯到遠端的節點上,我覺得可以是 IoT 系統的兩隻翅膀,有之 應該會具有相當的優勢。我們可以把Arduino 或者 ESP8266 這類的節點視為 Engine(Worker),可以由中央主機統一調度,發派任意的程式碼要求遠端執行。

 但是,要在 Arduino 上面用 C 語言建構這類的平台,至少對我來說是有點不敢想像的。




然而,現在有了 MicroPython



 之前參加 Taichung.py 的一場 Meetup, 由 Max Lai 解說 MicorPython 的技術應用,內容生動有趣,看到小小的 ESP8266 上居然可以跑 Python 的程式,覺得很神奇,但是一直到最近才有時間動手試試看。




  

  
NodeMCU簡介:

 Max Lai 使用的硬體是 NodeMCU ,基本上是一塊 ESP8266-12E 的模組加上 4MB 的 flash 和一些電源與USB-UART的線路所組成,可以用一般的 MicroUSB 線連接到電腦,就可以很方便地與電腦溝通,可以使用 C、Lua、Python (MicroPython 版本) 程式語言開發程式並燒錄到 ESP8266上,網路上可參考的範例很 多。

 
另外也有 D1 mini ,也是基於 ESP8266-12E,接腳較少但是體積更小巧一點。
HTML5 Icon

 

Python 與新的運作模式:

 既然現在有了 MicorPython, 在 ESP8266 上面可以實行 Python 的程式,我們是否可以利用 Python 的優勢,在使用 ESP8266 作為 IoT端點 的時候,具有以下的優點:
  • 可如同一般的 Arduino 一樣獨立運作, 不需要外力介入。
  • 也可以採取 Firmata 的模式,由遠端的主機透過過網路讀寫 ESP8266 上的 GPIOs,作相對的處理與控制,邏 輯上可以更複雜與彈性
  • 程式改版方便,可以透過網路即時佈署
  • 可以動態地傳送 任意的程式碼 要求遠端端點執行。
  • 提供 RPC 的機制。
  • 各節點可以互相彼此自主溝通。
 ESP8266 是用來作為 IoT端點的絕佳選擇,借助 Python 的優點,希望在建置私有 IoT系統的時候,可以比較方便快速。




實驗性小型系統

 於是,我花了幾天的時間依據 IPython Parallel 的精神, 寫了一個 Hub-Workers 架構的小型系統,把 NodeMCU 和 D1 mini 視為 Engine(Worker),並且提供了 RPC 的機制,node 之間可以互相溝通,管理者也可以派送任意的程式碼要求遠端節點執行,感覺上這樣方便多了,因為每個節點的行為可以透過網路動態的被調整。

 原本第一版的系統是以自建的一個 socket server 作為 broker 來收發訊息,但是因為只有提供 QoS level 0 的服務,後來還是改用 Mosquitto 作為 broker,遵循 MQTT 協定,惟 MicroPython 官方所提供的 MQTT client 端 library 的限制,只能支援到 QoS level 1。以 Mosquitto 來做 broker 也更能融入既有的系統環境,相容性好資源豐富,好處多多。



相關準備與設定

config.py

 基本上只需要修改 BROKER_HOST 的位址即可,ip 或 domain name 皆可。
 各節點連上網路之後,會嘗試跟 broker 連線,因此如果 broker 具有 public ip,應該就可以不受防火牆的限制。

# filename: codes/shared/config.py
...
# Client ************************
# Must config ******************
BROKER_HOST = '192.168.0.114'
# Must config ******************

燒錄 MicroPython 到 NodeMCU 上

設定 NodeMCU 連上 WiFI 網路

 將 MicroPython 燒錄進去之後,Serial 連線進去就會看到 Python 的 prompt >>>
 執行這一行指令就可以了 (SSID 和 password 要填):

 import network; nic=network.WLAN(network.STA_IF); nic.active(True); nic.connect('SSID','password');nic.ifconfig()

上傳基本程式到 NodeMCU

 實驗過程中需要常常上傳 Python 程式檔案到 NodeMCU 上面,所以我寫了一個 IPython Notebook 來自動化:

 notebooks/上傳檔案到 NodeMCU (Upload files to NodeMCU).ipynb

啟動 Broker

 都準備好了之後,就把 Broker 叫起來,我是在 Raspberry Pi 上面 run 一個 Mosquitto 的 Docker container,可以參考 這裡 所述的方法。


測試:

對遠端節點讀寫 GPIO

 Client 可以透過網路對遠端的節點做以下的事情:
  • 讀寫 GPIO
  • 傳給遠端節點 一段任意的程式碼並要求節點執行
  • 上傳檔案給遠端節點,檔案若是 Python 程式碼,並可以要求遠端節點 import 並執行
    基於上述的機制,等於是可以做任何 節點的硬體能力範圍內的事情。
 我把一些範例都放在這個範例程式碼 ( 2_start_client.py ) 內,直接執行就可以了。
 這個範例城市開始執行時會先透過一個 MQTT 的 topic 廣播 點名要求,要求上線的節點都發訊息過來報到,因此可以很方便地收集上線節點的名單。



範例程式碼中提供一些用法案例,例如:

這個訊息,要求遠端節點閃燈 3次。
messages['blink_led'] = {'type': 'command',
 'command': 'blink led',
 'kwargs': {'times': 3, 'forever': False, 'on_seconds': 0.05, 'off_seconds': 0.05}}


由 PC 對遠端 6 個ESP8266模組做讀寫,並遙控遠端節點閃動 LED。



這個訊息,要求遠端節點回傳 GPIO pins 的狀態。
messages['read_GPIOs'] = {'type': 'command',
 'command': 'read GPIOs',
 'need_result': True}

這個訊息,要求遠端節點依序 設定指定 pin 的 value (pin 2 代表 on board LED)。
messages['write_GPIOs'] = {'type': 'command',
 'command': 'write GPIOs',
 'kwargs': {'pins_and_values': [(2, 0), (2, 1), (2, 0),]}}


這個訊息,要求遠端節點 evaluate '2+3',並傳回結果。
messages['test eval'] = {'type': 'eval',
 'to_evaluate': '2+3',
 'need_result': True}


這個訊息,要求遠端節點執行一個 statement。
messages['test exec'] = {'type': 'exec',
 'to_exec': 'print("Testing exec !")'}


以上訊息寫好之後,就要求 client 將之傳送給遠端的節點。
client.request(remote_node, message)


遠端佈署程式,讓節點自行運作

 如果主機很忙,希望節點可以獨立自主運作,那也可把控制邏輯放在一個 while True 的迴圈中,並將程式碼遠端佈署給節點執行,節點就會進入自主運作的狀態 (不過 watch dog 或其他中斷的機制需先設定好)。


例如,我們可以將以下程式碼,遠端佈署給節點執行,這 段程式碼象徵性的會讓 LED閃個不停。
# filename: codes/_demo/script_to_deploy.py
print('_______ testing remote deploy ______')
print('_______ deployed from remote _______')

import machine
import time

def blink(pin, on_seconds = 0.5, off_seconds = 0.5, on = 0, off = 1):
 pin.value(on)
 time.sleep(on_seconds)
 pin.value(off)
 time.sleep(off_seconds)
def main():
 on_board_led = machine.Pin(2, machine.Pin.OUT) 
 while True:
 blink(on_board_led) # 主要邏輯放這邊 

# main() will be invoked after this script is uploaded.
main()


Script 檔案準備好之後,就在 client 端用以下的程式把檔案上傳到遠端節點並令其執行,LED燈就會閃個不停了。
with open('script_to_deploy.py') as f:
 script = f.read() 

messages['test upload script'] = {'type': 'script',
  'script': script}

client.request(remote_node, message)

但是就如之前所說的,還是需要在硬體的能力範圍之內,ESP8266上面只有 160KB的 RAM,硬塞大檔案當然就是會當機。




Summary

 這次實驗藉由 Broker-Workers 的架構,以 Mosquitto 作為 MQTT 的 broker,並參考 IPython Parallel 的功能,並提供 RPC 的機制,讓在主機上的 client 端可以透過網路讀寫遠端 ESP8266 模組上的 GPIO 並加以控制,並且,可以動態傳遞一段程式碼要求節點執行,節點也可以藉由執行主機交付的迴圈程式而成為一個獨立運作的單元。


 跟 IPython Parallel 不同的是,本系統中的 Client 端並不直接跟 Hub 溝通,Client 端其實是透過一個 local node(worker)來跟其他節點溝通,node 和 node 之間可以溝通,並不需要 client 端的介入。

 MicroPython 的出現或許會帶來另外一股風潮,除了程式撰寫之便以外,受惠於 Python 語言的特性,末端裝置或許將會更加彈性可塑,這也令人期待更加蓬勃發展的物聯網。

參考資料


ESP8266 WiKi
NodeMCU WiKi
A python proxy in less than 100 lines of code
Boards Running MicroPython Asynchronous Socket Programming
ESP8266 first project: home automation with relays, switches, PWM, and an ADC