[IoT] ESP8266+BroadLink實現進出房間自動開關燈 - (4) 程式實作

解釋了這麼久,總算到最重要的程式範例部份了

基礎版程式

NodeMcu-RoomControl-noLFS - 這是我目前程式的簡化版,只保留了基本的感應開關燈的功能。你需要作的是在config.lua中填入你家的Wi-Fi SSID、密碼,以及開關房間燈的GET網址,然後觸發一下兩個人體紅外線感應器,成功的話它就會GET開啟/關閉的網址來開/關燈。

NodeMcu預設會在執行wifi.sta.config({ ssid = config.ssid, pwd = config.pwd })後便自動開始連線至Wi-Fi,其上的wifi.eventmon.register是註冊Wi-Fi狀態改變的事件。官方建議在連上Wi-Fi後再載入主程式,以延緩因程式寫錯而發生的PANIC loop,故此wifi.lua中我在連線成功(GOT_IP)時呼叫了一個global callback afterWifiConnected(),並在init.lua中覆寫來載入其它lua script。

gpioTrig.lua中的regIoTrig()函式是用於註冊io腳的上緣/下緣中斷,也就是當接腳電位從低變高/高變低的時候會執行callback。這邊是讀取當前接腳的電位,若在低電位便註冊上緣,反之註冊下緣中斷。

而之所以要開出一個簡化版的分支呢,就是因為主線用了官方的另一個技術...

LFS

Lua Flash Store,用於解決在ESP8266執行lua時導致ram不足的問題。

當你在玩NodeMcu時,可能會注意到UART有時會印出not enough memory的訊息並停止執行。面對這情況最直接的解決方法是把esp重開,然後一一執行你的各個lua腳本,再使用node.heap()來確認當前剩餘多少可用的ram。

lua不像C語言程式那樣函式是不耗記憶體的,lua是會在你宣告全域函式的同時便會將那整個函式,包括其中的字串全都存進記憶體。dofile()也只是把你上傳到SPIFFS(SPI Flash File System,NodeMcu拿來存lua script的位置)拿出來執行而已,並不會讓你減少記憶體用量。於是你變會發現程式執行越多的同時,可用的記憶體量會快速的減少,即便你沒有使用全域變數,光是那些全域函式就很佔空間了。

一般系統重開後尚有40k的ram可使用,大約用到剩15k時便容易在執行程式時發生記憶體不足的狀況,並可能進一步導致系統出錯、重啟(印出PANIC)。

於是NodeMcu便在2.0之後推出了LFS這個技術,讓你能直接將函式及字串改存至flash儲存空間中,並把那些函式的指標指向flash的位置。當你執行函式時,它便會優先index到存於flash資料位置中的函式來執行,讓你不必耗費任何記憶體也能載入大量的程式。

它的另一項功能是預載字串池(String pool)可能出現的字串。寫過C#的應該知道每個字串在建立時會先檢查字串池中有沒有相同字串,有的話便不會建立並直接回傳該字串的參考位置。在lua中也用了同樣的原理,所謂的預載,就是將LFS中dummy_strings.lua寫到的字串載入到字串池中,當程式建立字串時便優先從LFS的資料位置尋找,這時當你建立相同的字串時,就不會再多耗費任何記憶體了。

除了一般的字串,當你呼叫任何函式時,也會在字串池建立該函式/物件名稱的字串,像是gpio.write就會在字串池加入"gpio"、"write"。所以在dummy_strings.lua一樣可以會需要將函式名納入。

至於那些preload的找法,我照著官方文件utils.lua寫了printRam()函式,會將執行當下存ram裡面所有的字串印出至ram.txt中,再將其中的字串加入後重編LFS img即可。之所以不直接印出整行文字而是寫至文字檔,是因為有時執行時會記憶體不足。

雖說LFS在節省ram真的能幫上很多忙,但不免得還是有些缺點

  • 速度慢: 由於不是從ram讀取而是flash,因此對時間敏感的函式、以及其中出現的字串函式名都應避免放進LFS以免出錯,像是與i2c傳輸有關的操作
  • 載入不如直接傳lua script靈活: 更新後得重新打包.img、上傳、執行node.flashreload(),且會忽略SPIFFS中的lua script(或者你可以自己改寫_init.lua)。一般只放改動很少的lua script
  • 需花時間理解: 第一次看到LFS這詞以及簡介時會非常的霧煞煞,需要花時間看完整份文件才能真正了解它在作什麼


建立LFS image

1. 將dummy_strings.lua以及其它要放入LFS的lua script壓縮成lfs.zip
2. 使用NodeMcu提供的雲端服務來編譯成img,選擇檔案後按REMOVE LUAC.CROSS (MASTER)
3. 使用ESPlorer上傳該.img至esp,並執行node.flashreload('lfs.img')

這項操作有時會提示記憶體不足的訊息,這時你可以選擇把init.lua清空並重啟,然後再載入一次img

主程式

NodeMcu-RoomControl - 與簡化版主要的差異如下

  • 新增ide.lua: nodemcu-web-ide,開80埠讓你能在線上編輯lua script而不一定得接USB線才能改程式。可以對SPIFFS中的文字檔新刪讀寫以及dofile(),並可以使用開啟網址的方式來dofile、讀取檔案內容。稍顯不足的是它不支援上傳一般檔案(如lfs.img)。這隻腳本非常吃記憶體,不放LFS很容易爆ram加上PANIC
  • 新增panicLoopGuard.lua: 啟動時建立名為booting的檔案並排程於於1秒後刪除,下次啟動(或因出錯而重啟時)會檢查若檔案,若還在則視為上次開機失敗、發生PANIC loop而不繼續dofile
  • wifiConn.lua: 加入updateTime(),從網路api取得時間並用rtctime模組來儲存
  • gpioTrig.lua: 加入ignoreOneTrig(),以避免你家的誰進房間來"關心"你的時候讓燈自動關了
  • utils.lua: 加入log(),以CSV格式儲存於log.txt,可以藉由web-ide來讀取其中內容。加入loadImg()來自動重啟並載入lfs.img
  • lcd1602.lua放至LFS,與i2c相關的移至lcd1602_setup.lua照一般方法執行,以避免出lcd顯示出亂碼

雲端更新lfs.img

lua script的更新可以靠web-ide,而lfs.img的更新就得讓esp自己去下載那檔案。我在wifiConn.lua有一個download()的函式,給它傳入你更新檔的網路位置即可下載。如果有現呈的NAS的話可以直接丟上去讓esp載,沒有的話可以自己電腦裝一下json-server並開Static file server給它載。

 

其它寫NodeMcu的技巧請參考幾個NodeMcu開發技巧