nodemcu 開發篇(一) - 血淚史
一開始就寫血淚史會不會讓人覺得很挫敗?
只是想在開發之前把需要注意的,會用到的一次寫完而已。
1. IDE 是使用 ESPlorer,firmware 是透過 Cloud Build Service 產生的。
記住接下來說的兩點 (血淚點)
a. 執行 ESPlorer 時,要用root 執行,不然會選不到裝置
ex: sudo java -jar ESPlorer.jar
b. baud rate 只能選 115200
2. 這次開發的功能主要有三項
a. 門窗開關感測功能,也就是 gpio 功能
b. 開啟 net server 功能,用來做開發版設定
c. 發送 Line 訊息 (https)
這三項足以搞死我,其實問題就只有一個,之前開發 windows form、web form、android、python、shell script 都沒有仔細想過的問題,就是開發版的記憶體太小了。
要提供這三項服務是非常吃重的,於是乎延伸出 css、javascript、html compress 的需求,還有對 lua 語言的進一步的認識。
關於上述 a. 這一點沒有記憶體的問題,網路上提供的範例有另一個致命的問題,在下面第3點說明。
關於上述 b. 這一點,原本希望提供豐富的網頁讓前端做一些設定,但是 net.createServer 會吃太多記憶體,測試每個網頁超過 2k 就會出現 out of memory,然後就重開機了。
所以做了以下處理,首先醜化每個網頁,不要用太多 css,什麼 round shape 什麼 transparent 什麼 webkit 都不要用。javascript 能簡化就簡化。html 越是精簡越好。
再搭配 css compress、javascript compress、html compress 三大工具,力求壓縮的極致。
關於上述 c. 這一點,開發的過程使用 http 或是 net.createConnection 的方式都試過,後來才發現最大的問題是 https,大多只能發成功第一次,第二次就會重開機了,也是記憶體問題。最後只好不用 https,改用家裡 nas 充當 web server,接收 http request 轉發 https request。
最後說到 Lua 語言的特性,就是關於變數,請記得宣告變數時要加上 local,除非是全域變數(盡量少用),因為 Lua 預設所有變數只要沒有 local,就都是全域變數,連在 local function 裡面的變數,沒加上 local 就還是全域變數。再來,如果有習慣把不同的模組用檔案去區分,你就會用到 require,但是 require 一用下去就是全域變數,會佔不少記憶體;這一點也是非常血淚,你可以使用這個方法。
假設檔案叫 func.lua 內容如下
local module = ...
return function()
end
在使用到這個模組的功能時,可以用 local 去承接這個 function,就我的經驗記憶體會再省一點。
ex:
local doSomething = require("func")
doSomething()
另一個經驗分享是 lua 檔案越小越好,upload 到開發版時,比較不會寫入失敗。
3. debounce
在測試的過程,不明原因開發版運作過程,經常沒有接收到 Line 訊息 (我希望開啟門窗時發送 line 訊息),通常都是運作一陣子之後才會發生,原本以為是記憶體耗盡的關係,後來才發現是 debounce 的緣故。
gpio 接收感測器資料時,常會需要 debounce,debounce 是因為感測器無法分辨訊號是一個還是多個?例如按按鈕,雖然我們只按了一次,但是按鈕的電位變化是多次,所以會在短時間送出許多訊號,這時候就需要 debounce 讓開發版知道在一段時間裡發生的多個訊號,只要計一次就好。
根據門窗感測器的網路上範例,在 debounce function 大概是這樣寫的
function debounce (func)
local last = 0
local delay = 10000
return function (bounceCheck)
local now = tmr.now()
local delta = now - last
if delta < delay then return end;
last = now
return func(bounceCheck)
end
end
看起來好像沒有問題,但是這段代碼忽略了一件事情,tmr.now() 在文件上有寫到這段文字
Returns the system counter, which counts in microseconds. Limited to 31 bits, after that it wraps around back to zero. That is essential if you use this function to debounce or throttle GPIO input.
天呀!如果最後一次觸發 debounce 是在 tmr.now() = 2^31 次方左右,那 last 變數會變成極大數,等到下次觸發 debounce 時,tmr.now() 已經歸零,delta 變成負值,就會好一陣子都不會觸發 func(bounceCheck)。
邏輯問題是很難 debug 的。
(Sorry... 我應該好好看代碼,不該抄完就不理,太過相信這些網路上的代碼了)
只是想在開發之前把需要注意的,會用到的一次寫完而已。
1. IDE 是使用 ESPlorer,firmware 是透過 Cloud Build Service 產生的。
記住接下來說的兩點 (血淚點)
a. 執行 ESPlorer 時,要用root 執行,不然會選不到裝置
ex: sudo java -jar ESPlorer.jar
b. baud rate 只能選 115200
2. 這次開發的功能主要有三項
a. 門窗開關感測功能,也就是 gpio 功能
b. 開啟 net server 功能,用來做開發版設定
c. 發送 Line 訊息 (https)
這三項足以搞死我,其實問題就只有一個,之前開發 windows form、web form、android、python、shell script 都沒有仔細想過的問題,就是開發版的記憶體太小了。
要提供這三項服務是非常吃重的,於是乎延伸出 css、javascript、html compress 的需求,還有對 lua 語言的進一步的認識。
關於上述 a. 這一點沒有記憶體的問題,網路上提供的範例有另一個致命的問題,在下面第3點說明。
關於上述 b. 這一點,原本希望提供豐富的網頁讓前端做一些設定,但是 net.createServer 會吃太多記憶體,測試每個網頁超過 2k 就會出現 out of memory,然後就重開機了。
所以做了以下處理,首先醜化每個網頁,不要用太多 css,什麼 round shape 什麼 transparent 什麼 webkit 都不要用。javascript 能簡化就簡化。html 越是精簡越好。
再搭配 css compress、javascript compress、html compress 三大工具,力求壓縮的極致。
關於上述 c. 這一點,開發的過程使用 http 或是 net.createConnection 的方式都試過,後來才發現最大的問題是 https,大多只能發成功第一次,第二次就會重開機了,也是記憶體問題。最後只好不用 https,改用家裡 nas 充當 web server,接收 http request 轉發 https request。
最後說到 Lua 語言的特性,就是關於變數,請記得宣告變數時要加上 local,除非是全域變數(盡量少用),因為 Lua 預設所有變數只要沒有 local,就都是全域變數,連在 local function 裡面的變數,沒加上 local 就還是全域變數。再來,如果有習慣把不同的模組用檔案去區分,你就會用到 require,但是 require 一用下去就是全域變數,會佔不少記憶體;這一點也是非常血淚,你可以使用這個方法。
假設檔案叫 func.lua 內容如下
local module = ...
return function()
end
在使用到這個模組的功能時,可以用 local 去承接這個 function,就我的經驗記憶體會再省一點。
ex:
local doSomething = require("func")
doSomething()
另一個經驗分享是 lua 檔案越小越好,upload 到開發版時,比較不會寫入失敗。
3. debounce
在測試的過程,不明原因開發版運作過程,經常沒有接收到 Line 訊息 (我希望開啟門窗時發送 line 訊息),通常都是運作一陣子之後才會發生,原本以為是記憶體耗盡的關係,後來才發現是 debounce 的緣故。
gpio 接收感測器資料時,常會需要 debounce,debounce 是因為感測器無法分辨訊號是一個還是多個?例如按按鈕,雖然我們只按了一次,但是按鈕的電位變化是多次,所以會在短時間送出許多訊號,這時候就需要 debounce 讓開發版知道在一段時間裡發生的多個訊號,只要計一次就好。
根據門窗感測器的網路上範例,在 debounce function 大概是這樣寫的
function debounce (func)
local last = 0
local delay = 10000
return function (bounceCheck)
local now = tmr.now()
local delta = now - last
if delta < delay then return end;
last = now
return func(bounceCheck)
end
end
看起來好像沒有問題,但是這段代碼忽略了一件事情,tmr.now() 在文件上有寫到這段文字
Returns the system counter, which counts in microseconds. Limited to 31 bits, after that it wraps around back to zero. That is essential if you use this function to debounce or throttle GPIO input.
天呀!如果最後一次觸發 debounce 是在 tmr.now() = 2^31 次方左右,那 last 變數會變成極大數,等到下次觸發 debounce 時,tmr.now() 已經歸零,delta 變成負值,就會好一陣子都不會觸發 func(bounceCheck)。
邏輯問題是很難 debug 的。
(Sorry... 我應該好好看代碼,不該抄完就不理,太過相信這些網路上的代碼了)
留言
張貼留言