我打算在ESP32控制板顯示OpenWrt路由器的運作時間、溫度、剩餘記憶體、已使用的記憶體百分比和用戶端數量(即:無線網路連線數)。
取得無線網路用戶端數量
Linux系統的iw命令(’i’代表interface,介面,’w’意指wireless,無線)用於設定和顯示無線裝置;搭配 ’dev’ 參數(代表device,裝置),可列舉路由器的無線網路晶片(phy,意指”physic”,實體)。例如,我的OpenWrt路由器(紅米AX6000)有5GHz和2.4GHz兩個頻段,因此會列出兩個網路晶片:

在終端機執行 ‘iw dev wlan0 station dump’ 命令,可呈現接入此 ‘wlan0’ 網路的所有用戶端資訊,包括它們的MAC位址、訊號強度(RSSI)、資料傳輸速率…等,像這樣:

因此,計算傳回的“Station”行數,就能得知Wi-Fi連線用戶數,這個需求可用grep命令達成,它有個計算內容行數的“c”參數(意指counter,計數)。同樣以讀取虛構的syslog檔為例:

底下敘述可計算其中包含‘Bluetooth’字串的行數:

若僅搜尋「以’Bluetooth’開頭」的字串,則要搭配規則表達式的’^’符號:

使用管線(|)轉交資料
假如省略grep的「檔案」參數,則代表讀取來自命令行的輸入。底下是搭配‘|’(管線)運算子,統計接入wlan0無線網路的用戶端數量的命令。管線‘|’負責把前一個命令的輸出,交給下一個命令處理。
Linux系統的殼層(shell)可執行腳本程式(shell script),達成自動化功能。底下是個設定變數的腳本程式敘述,在路由器終端機輸入這一行,將能取得連線到 “wlan0” 的裝置數量,並存入COUNTS變數。
COUNTS=$( iw dev wlan0 station dump | grep -c '^Station' )

“$( .. )”稱為「命令替換(Command Substitution)」,其作用是執行小括號裡面的敘述並傳回執行結果,在此是存入自訂變數。
「命令替換」的早期寫法是用反斜線(`)符號,因此上面的敘述可寫成:
COUNTS=`iw dev wlan0 station dump | grep -c '^Station' `
在終端機輸出變數值的命令是echo,底下的輸出代表有7個連線裝置。

我的路由器有wlan0和wlan1兩個無線介面,所以實際的連線裝置數量是這兩個介面的總和。若直接用 ‘+’ 運算子相加兩個變數,得到的結果將是相連的字串,如下圖左:

正確的辦法是用「算數展開(Arithmetic Expansion)」符號,也就是 $(( 和 )) 包圍運算式,如上圖右。但算數展開式大都僅支援整數,以及簡單的四則運算,若運算元包含浮點數字,會產生語法錯誤:

如果需要進行浮點數或複雜運算,可改用awk命令或者bc(代表basic calculator,基本計算機)工具程式。底下命令將統計wlan0和wlan1連線裝置的總和:
COUNTS=$(( $( iw dev wlan0 station dump | grep -c '^Station' ) + $( iw dev wlan1 station dump | grep -c '^Station' ) ))

執行結果:
編寫腳本程式
本單元將開始編寫一個Linux腳本程式,讓連線裝置或程式(如:網頁瀏覽器、ESP32控制板或者Python和JavaScript等程式)執行並取得即時資料。
首先要決定腳本程式的資料輸出格式,像底下的純文字形式,或者展示成HTML網頁,這兩者都方便人類閱讀。
運作時間:9天 12時34分56秒 CPU溫度:45.38°C 可用記憶體:336.29MB 已使用記憶體百分比:29.6% 連線裝置數:16
若是為了方便機器(如:ESP32和Arduino)解析資料,應該採用標準資料交換格式,例如JSON或XML。因為它們能用結構化的方式描述資料,並且方便程式解析。底下是JSON格式的例子:
{
"uptime":"9天 12:34:56",
"temp":"45.38°C",
"ram_avail":"336.29MB",
"ram_percent":"29.6%",
"clients":16
}
上面的 ”uptime”(運作時間)資料,把原本的秒數,轉換成比較容易理解的「天 時:分:秒」格式,可以用底下的awk命令達成:

使用cat命令輸出多行文字
假設程式只需要輸出單一元素的JSON資料,像這樣:
{ "uptime":"9天 12:34:56" }
可以用一行printf(格式化輸出)敘述完成,像下圖左,請注意最後的變數名稱參數要用雙引號包圍,否則printf會以資料裡面的空格為分界,拆解成不同的輸出。
在需要輸出多行內容的場合,使用cat命令比較方便,程式也更易讀。底下是採用cat命令的語法格式和輸出結果,包含「連線裝置數」和「運作時間」兩個元素的JSON資料。

其中的 “<<” 稱為here-doc(直譯為「此處-文件」)運算子,代表「從下一行到分界標記結束,把全部內容傳給標準輸入裝置(也就是終端機)」。
「分界標記(delimiter)」習慣上命名為EOF(意指“end of file”,檔案結尾),但可以用其他英文字母(大小寫有別),不包含空白和特殊字元。內文最後的結束標記必須單獨佔一行,前後不能有空格。
uHTTPd網站伺服器與CGI腳本
讓外部裝置(如:Arduino)執行OpenWrt路由器上的程式,取得它的運作狀態,最簡單的辦法是提供CGI腳本(CGI script)。CGI腳本指的是在HTTP網站伺服器端運作的程式,類似Python的Flask網站程式,預設存放在OpenWrt系統的 “/www/cgi-bin/” 目錄裡面。
假設「取得路由器狀態」的CGI腳本名叫 ”status”,而路由器的網址是192.168.1.1,外部裝置將能透過這個網址執行它:
http://192.168.1.1/cgi-bin/status

根據OpenWrt官網“uHTTPd webserver”文件的說明,OpenWrt主流版本的系統映像檔通常預設包含名叫uHTTPd的HTTP伺服器軟體套件;如果沒有,可以透過底下命令自行安裝:
opkg update opkg install uhttpd
我的紅米AX6000路由器安裝的是在恩山無線論壇,”bleach1991”編譯的版本,每個月大約更新三次,內含uHTTPd伺服器。
編寫與執行CGI腳本
底下是在瀏覽器顯示一個大標文字 “hello world!” 的基本CGI腳本程式。腳本程式的第一行必須是由井號和驚嘆號起頭的「直譯器指令(Shebang)」。

HTML標頭的相關說明,請參閱《超圖解Arduino互動設計入門》第18章的「認識 HTTP 通訊協定」單元。
請將這個程式碼存入 “/www/cgi-bin/” 路徑底下的 “hello” 文字檔。以使用nano文字編輯器建立此文件為例,請先在連線OpenWrt的終端機輸入底下命令安裝nano:
opkg update opkg install nano
接著執行底下命令,用nano開啟並新增 “hello” 文件:
nano /www/cgi-bin/hello
然後在nano編輯器裡面貼入底下的原始碼(在Windows的終端機中,按一下滑鼠右鍵即可貼入):
#!/bin/sh echo "Content-type: text/html" echo "" cat <<EOF <html><body> <h1>hello world!</h1> </body></html> EOF
像這樣:

最後,按下Ctrl+O寫入檔案、按下Ctrl+X退出nano。
此時,若在瀏覽器中輸入:“http://192.168.1.1/cgi-bin/hello” 網址,將會看到如下的「禁止(Forbidden)」錯誤訊息,代表你沒有執行 “/cgi-bin/hello” 檔案的權限。

回到終端機,輸入底下命令,賦予 “/www/cgi-bin/hello” 檔案執行的權限;此即chmod +x的作用,chmod代表 “change mode”(改變模式),“+x” 代表「賦予執行(execute)權限」。
root@BleachWrt:~# chmod +x /www/cgi-bin/hello
重開網頁,即可正常執行:

顯示OpenWrt路由器狀態的完整CGI腳本
底下是傳回JSON格式,OpenWrt路由器狀態的完整CGI腳本原始碼,第2行的內容類型(Content-Type)標頭要設成 "application/json"。
#!/bin/sh
echo "Content-type: application/json"
echo ""
# 取得「運作時間」
UPTIME=$(awk '{
sec=int($1);
d=sec/86400;
h=(sec%86400)/3600;
m=(sec%3600)/60;
s=sec%60;
printf("%d天 %02d:%02d:%02d", d, h, m, s)
}' /proc/uptime )
# 取得「處理器溫度」
TEMP=$(
awk '{printf "%.2f°C\n", $1 / 1000}' /sys/class/thermal/thermal_zone0/temp
)
# 取得「可用記憶體大小」
RAM_AVAIL=$(
awk '
/MemAvailable/ {printf "%.2f MB\n", $2/1024}
' /proc/meminfo
)
# 取得「已使用的記憶體大小」
RAM_PERCENT=$(
awk '
/MemTotal/ {t=$2}
/MemAvailable/ {a=$2}
END {
printf "%.1f%%\n", (1 - a/t) * 100
}
' /proc/meminfo
)
# 取得「連線裝置數量」
COUNTS=$((
$( iw dev wlan0 station dump | grep -c '^Station' ) +
$( iw dev wlan1 station dump | grep -c '^Station' )
))
cat <<EOF
{
"uptime":"$UPTIME",
"temp":"$TEMP",
"ram_avail":"$RAM_AVAIL",
"ram_percent":"$RAM_PERCENT",
"clients":$COUNTS
}
EOF
在終端機執行底下命令,用nano開啟、新增 “status” 文件:
nano /www/cgi-bin/status
貼入上面的原始碼之後,按下Ctrl+O寫入檔案、按下Ctrl+X退出nano。
最後,執行底下命令賦予 “status” 執行權限:
chmod +x /www/cgi-bin/status
用瀏覽器開啟 “http://192.168.1.1/cgi-bin/status” 網址,即可看到JSON格式的路由器狀態資訊。

如此,就可以使用ESP32 Arduino程式連線、解析並讀取OpenWrt網站的JSON資料,相關說明與程式範例可參閱《超圖解ESP32深度實作》第7章,也可以用Python請求(request)此JSON資料,再解析處理,請參閱《超圖解Python程式設計》第10章說明。
