在 NodeMCU (ESP8266) 這樣玩定時~

一、前言

今天要跟大家介紹一個使用在 nodemcu (on ESP8266 WiFi SoC) 的定時模組,這個定時模組的API 使用了 javascript 風格,也就是 setTimeout(), setInterval() 這樣的介面,相信這對寫 javascript 或 node.js 的朋友,應該是再熟悉不過啦~
 
其實 nodemcu 的 tmr 模組所提供的 7 個定時器,在一般的定時應用已經蠻足夠了,只不過,用起來還是會有一點小煩阿。如果現在同時只有 2, 3 個定時工作要跑,那還OK。但如果你現在同時有10個、20個定時工作要跑,那你肯定是要GG一陣子,你必須得好好管理這7個定時器,誰在重複執行、誰在timeout後釋放了。接著,你就需要在定時工作中,用一、兩個變數充當子計數器,結果搞得程式碼完全爛掉,而且還不能重用,你說痛不痛苦。
 
這個模組的出現,其實是我在另一個專案上為了要解決多重 requests 的逾時控制而誕生的,我深深地被 tmr 折磨了好一下子,只好那個捏著,自己寫一個通用型的定時模組,以解決我所遇到的痛處。我不知道是不是早有類似的模組存在,如果有的話,那很好!表示不只我會遇到這種痛苦。
 
至於nodemcu-timer的實作細節,我就不贅述啦!如果對此模組細節有興趣的朋友,我在這裡小小提醒一下,雖然風格是仿照 node.js,但是此函式庫中的 setImmediate() 的行為跟 node.js 還是有一點小小的差異。畢竟,那可是有事件迴圈的node.js啊!這只是一個小小的定時器啊!科科~
 

二、nodemcu-timer 簡短介紹

重點只有4支APIs:setTimeout(), clearTimeout(), setInterval(), clearInterval()
他們分別用於啟動、取消單次定時跟重複定時
 
當目前沒有任何定時工作時,timer 會自動停止內部的計數器,一旦你用setTimeout與setInterval將工作推入定時器,內部定時器會自動啟用,你不用擔心定時器什麼時候會動、什麼時候不會動。

三、下載 nodemcu-timer module

1. 到 nodemcu-timer github,看你要 clone 還是下載 .zip 檔回來都可以
2. timer.lua 跟 timer_min.lua (minify過的版本) 自己挑一支用,因為等一下會 compile 成 bytecode,所以用哪一支都沒差。如果你不想 compile,那我是建議使用 minify 過的那支script。
3. 下載到 ESP8266


4. 寫一支 app.js,等一下下載到 ESP8266
    記得在 init.lua 中,do 你的 app.js


四、app.lua 示範

這裡還是拿大家用到爛掉的 LED 來做示範,你手邊若沒有 LED,那用 print() 在 terminal 觀察文字提示也可以啦!

4.1 LED定時亮滅

這支程式大家一定看得懂,LED會亮1秒、暗1秒,一直重複。API 的介面是 timer.setInterval(callback, delay),callback 是你的task,dealy是重複週期,單位是ms。我的LED是接成 active-low。

local timer = require 'timer'

local LED_PIN1 = 0
gpio.mode(LED_PIN1, gpio.OUTPUT)

local sw1 = true
timer.setInterval(function ()
    if (sw1) then
        gpio.write(LED_PIN1, gpio.LOW)
    else
        gpio.write(LED_PIN1, gpio.HIGH)
    end
    sw1 = not sw1
end, 1000)


4.2 LED提示燈

現在這支程式的目標是設計一支負責點亮LED的函式,介面是 blinkLED(led, times, interval),你可以選擇要哪一顆 LED (led) 以多快的速度 (interval) 閃爍個幾次 (times)。
 
現在我要用它來同時點亮閃爍速率跟次數都不一樣的 3 顆 LED。這邊值得一提的是,blinkLED 使用了 Lua 的閉包(closure)性質,然後 blinkLED() 是可重進入的,你不需要自己管理 timer id 來管理定時器資源。一些觀念上的東西,我也不在此唬爛一拖拉庫啦~ 其實有在用的人應該都知道啦~我還是把重點放在模組的使用。
 
在你執行 app.lua 之後,程式會分別在 1234ms, 3528ms, 5104ms 之後分別觸發三顆 LED 的閃爍。他們各以不同的速度閃爍了 5 次、3 次與 10 次。

local timer = require 'timer'

local LED_PIN1, LED_PIN2, LED_PIN3 = 0, 1, 2

gpio.mode(LED_PIN1, gpio.OUTPUT)
gpio.mode(LED_PIN2, gpio.OUTPUT)
gpio.mode(LED_PIN3, gpio.OUTPUT)

function blinkLED(led, times, interval)
    local sw, count, tobj = true, 0

    tobj = timer.setInterval(function ()
        if (sw) then
            gpio.write(led, gpio.LOW)
        else
            gpio.write(led, gpio.HIGH)
            count = count + 1
        end
        sw = not sw
  
        if (count == times) then
            timer.clearInterval(tobj)
            gpio.write(led, gpio.HIGH)
        end
    end, interval)
end

timer.setTimeout(function ()
    blinkLED(LED_PIN1, 5, 560)
end, 1234)

timer.setTimeout(function ()
    blinkLED(LED_PIN2, 3, 1024)
end, 3528)

timer.setTimeout(function ()
    blinkLED(LED_PIN3, 10, 200)
end, 5104)


示範結果:

五、後記

你可能會認為,不過就是閃爍個 LED 有啥了不起。科科~ 你可以試著用以上的條件,自己拿 tmr 模組寫寫看,假如你寫出來了,你可以再寫一支同時有 20 顆 LEDs 在活動的情況,你一定會覺得 nodemcu-timer 真好用,這正是定時功能被抽象化之後帶來的好處。
 
試想,當你希望在某種條件發生時,快速閃爍一顆紅色LED燈;在某種情況,又要慢速閃爍某顆LED燈;在另一種狀況又要....,恩,真的很煩~ 現在把閃爍LED想像成某一個工作,道理也是一樣的。
 
有了 nodemcu-timer,你的程式可以用比較簡潔的風格寫出比較複雜的邏輯。如果,你希望寫事件驅動式的應用程式,你可以在某種狀況時,發射某個事件,然後執行對應的監聽器,你的程式可以鋪陳地更有段落、更有組織、更婀娜多姿、更花枝招展哦!下次我再來介紹 lua-events 這個模組,跟 nodemcu-timer 搭配服用,效果更好!
 
(ps. 你可能會問說,啊 nodemcu 不是都標榜它是事件驅動風格嗎? 恩~ 關於這點,請自行體會囉~大多時間你都是在 listen 內建模組的事件,要用這些事件在你的 app-level 撰寫像樣的 event-driven 程式碼,恩....  再說一次,請自行體會~科科)
 
 
 
simen

An enthusiastic engineer with a passion for learning. After completing my academic journey, I worked as an engineer in Hsinchu Science Park. Later, I ventured into academia to teach at a university. However, I have now returned to the industry as an engineer, again.

2 Comments

  1. 你好 可以請教關於timeout,
    我的構想是用 一個tmr 定時 通知 observer 更新 觀察者的計數 達到計滿後 callback 送出 timeout
    計時器也可以很多個加入 如此題目 當長時間timeout 後 再新增 閃燈 的timeout 輸出gpio
    更甚 在timeout 中增加high. low 不同周期切換的方法 這樣會比較好嗎 謝謝指教

    ReplyDelete
    Replies
    1. Hello 您說的方式也OK, 但至於會不會比較好, 我還真的不好說ㄋㄟ~

      Delete
Post a Comment
Previous Post Next Post