顯示OpenWrt路由器或Linux裝置的狀態(二):建立CGI腳本程式

我打算在ESP32控制板顯示OpenWrt路由器的運作時間、溫度、剩餘記憶體、已使用的記憶體百分比和用戶端數量(即:無線網路連線數)。

取得無線網路用戶端數量

Linux系統的iw命令(’i’代表interface,介面,’w’意指wireless,無線)用於設定和顯示無線裝置;搭配 ’dev’ 參數(代表device,裝置),可列舉路由器的無線網路晶片(phy,意指”physic”,實體)。例如,我的OpenWrt路由器(紅米AX6000)有5GHz和2.4GHz兩個頻段,因此會列出兩個網路晶片:

iw dev命令

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

'iw dev wlan0 station dump' 命令

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

syslog

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

計算其中包含‘Bluetooth’字串的行數

若僅搜尋「以’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' )
))

統計wlan0和wlan1連線裝置的總和

執行結果:

統計wlan0和wlan1連線裝置的總和

編寫腳本程式

本單元將開始編寫一個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會以資料裡面的空格為分界,拆解成不同的輸出。

printf(格式化輸出)敘述

在需要輸出多行內容的場合,使用cat命令比較方便,程式也更易讀。底下是採用cat命令的語法格式和輸出結果,包含「連線裝置數」和「運作時間」兩個元素的JSON資料。

使用cat命令輸出多行文字

其中的 “<<” 稱為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

uHTTPd網站伺服器與CGI腳本

根據OpenWrt官網“uHTTPd webserver”文件的說明,OpenWrt主流版本的系統映像檔通常預設包含名叫uHTTPd的HTTP伺服器軟體套件;如果沒有,可以透過底下命令自行安裝:

opkg update
opkg install uhttpd

我的紅米AX6000路由器安裝的是在恩山無線論壇,”bleach1991”編譯的版本,每個月大約更新三次,內含uHTTPd伺服器。

編寫與執行CGI腳本

底下是在瀏覽器顯示一個大標文字 “hello world!” 的基本CGI腳本程式。腳本程式的第一行必須是由井號和驚嘆號起頭的「直譯器指令(Shebang)」。

“hello world!” CGI腳本程式

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

像這樣:

nano編輯器

最後,按下Ctrl+O寫入檔案、按下Ctrl+X退出nano。

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

禁止(Forbidden)錯誤

回到終端機,輸入底下命令,賦予 “/www/cgi-bin/hello” 檔案執行的權限;此即chmod +x的作用,chmod代表 “change mode”(改變模式),“+x” 代表「賦予執行(execute)權限」。

root@BleachWrt:~# chmod +x  /www/cgi-bin/hello

重開網頁,即可正常執行:

hello world! 網頁

顯示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格式的路由器狀態資訊。

JSON格式的路由器狀態資訊

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

Posts created 529

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top