うたカモ技術ブログ

Linux OpenWrt

OpenWrtアプリケーション開発   LuCI用カスタムページの作成と追加(難易度Lv1)

post:     update: 

この記事で紹介するソースコードは自由に使っていただいて構いません。 アプリケーションの開発や自己学習にお役立て下さい。ただし、当ブログでは掲載するソースコードを流用・利用したことによる損害等につきましては 一切の責任を負いません。自己責任で利用お願いします。

今回はOpenWrtアプリケーション開発として、自作のカスタムページをWebUIのLuCIに追加する方法を紹介します。

あくまでOpenWrtの既存機能やアプリケーションはそのままで、LuCIを通してユーザーに提供するページ(HTMLファイルやLuaスクリプト)を新たに作成・追加します。

このようなことから、この記事で紹介するカスタムページの実装難易度は最も簡単なLv1と定義します。

この記事を読んで恩恵がある読者は以下のような方です。

  1. これからOpenWrtのアプリケーション開発を学びたい人
  2. 自分にとって使いやすいユーザーインタフェース(設定画面)を作りたい人
  3. バグや機種未対応で正常に動作しないサービスに対して、適切にサービスを有効化できる設定画面を追加したい人

※3番目の方は、サービスが動作しない問題が設定パターンである場合に限ります。設定パターン以外の場合、標準アプリ自体のバグ (または、それに準ずるバグ)と考えられるため、ここで紹介する情報だけでは解決できません。

興味があれば、ご参考ください。

作成中の関連記事について

現在、本記事の関連として以下の記事を作成中です。この記事も合わせ、実装難易度を3段階に分けて紹介します。

目次

  1. 実施環境と補足事項について
  2. サンプルプログラムのダウンロード案内
  3. 今回作成するLuCIカスタムページの概要
  4. カスタムページ作成の流れ
  5. 本題:開発準備
  6. 本題:カスタムページの作成
  7. 配布方法①:セットアップスクリプトの作成と実行
  8. 配布方法②:カスタムページのパッケージ作成とインストール
  9. LuCIがシステム内部で利用するUCI操作プロセス(/sbin/rpcd)について
  10. 【おまけ】Luaスクリプト用UBUSモジュールでカントリコード一覧を取得する方法
  11. おわりに
  12. 参考文献

実施環境と補足事項について

この記事は以下の環境で実施した結果を元に作成しています。

#開発用PCの実行環境OS
ubuntu 22.04 LTS 64bit ※今回はUbuntuで開発しましたが、Windows(WSL2)上でもOKです。
#OpenWrtデバイス
  名称:Raspberry Pi3 Model B
  CPU:ARM Cortex-A53 (1.2GHz)
  SOC:Broadcom BCM2837
  RAM:1GB
  ストレージ(media):MicroSDカード
#OpenWrt
  OpenWrt 22.03
パッケージ
  luci - git-24.078.77116-81496b8
  luci-compat - git-24.078.77116-81496b8
  luci-lua-runtime - git-24.078.77116-81496b8

この記事で紹介するカスタムページの作成と追加方法に関して、機種依存はありません。 この記事ではRaspberry Pi3BをOpenWrtデバイスとしていますが、どんなデバイスでもOKです。(※LuCIがインストールされていることが前提です。)

[補足事項]
記事内で紹介するコンソール表記は次の通りです。

開発用PCのコンソール表記
ユーザー名とカレントディレクトリを色付けして表記します。
user:~/openwrt$ command
OpenWrtのコンソール表記
ユーザー名はroot@OpenWrtです。
root@OpenWrt:~# command

この記事が想定するOpenWrtデバイスの初期状態

この記事で想定するOpenWrtデバイスの状態は、OpenWrtがインストールされた直後の初期状態です。
IPアドレスに着目すれば、OpenWrtデバイス自身は192.168.1.1/24のIPアドレスを持ち、192.168.1.0/24のネットワークを管理しています。

サンプルプログラムのダウンロード紹介

この記事で作成するカスタムページのデータは以下の2通りの方法で入手できます。

セットアップスクリプトで自分のOpenWrtデバイスにインストールする

本記事のこちらを参照してください。

Gitリポジトリをクローンする

今回取り上げるLuCIのカスタムページ一式は私のGitHubリポジトリにあります。 パッケージ作成に必要なMakefileも揃った完全版です。

一応、この記事の内容に沿って1つずつ作業をしていけば、このリポジトリにあるものと同じものが作成できます。

記述ミスなどで躓いたら、以下のリポジトリをダウンロードして手っ取り早く動作を確認してみてください。

今回作成するLuCIカスタムページの概要

この記事で作成するLuCIのカスタムページは以下の動画で紹介するものです。例として、OpenWrt開発入門の第3回第4回の両方で紹介したアクセスポイント設定をカスタムページ経由で実施しています。

内部構成としては、既存のOpenWrtのLuCI環境にカスタムページを追加しただけです。

ネットワーク設定とWi-Fi設定のカスタムページを作りますので、ネットワーク・Wi-Fi制御用アプリケーションのnetifdが 管理する/etc/config/network/etc/config/wirelessの各オプションの値を変更・適用できます。

カスタムページ作成の流れ

この記事では、流れに沿って前節で紹介したLuCIのカスタムページを作成・追加します。

先ず開発準備として、OpnWrtデバイスに対してファイル転送する環境を整えた後、カスタムページ用のHTMLファイルやLuaスクリプトを 流し込んで動作を確認します。

全ての動作確認が取れたら他のユーザーに配布できるように、それらのファイルをセットアップスクリプトやアプリケーションパッケージにまとめます。

このように、本筋のカスタムページの作成と追加を一番簡単な方法で先に説明し、最後にセットアップスクリプトとパッケージの作成方法を紹介することで 一通りの開発方法が分かる構成になっています。

それでは、次節から本題に入ります。

本題:開発準備

LuCIのカスタムページを作成する前に開発準備をしましょう。

準備1:OpenWrtのインストール 

当然ですが、まずはOpenWrtデバイスを用意しましょう。一応、私の記事にはRaspberry Pi1~5を対象にOpenWrtファームウェア をインストールする方法を紹介していますので、良ければ参考にしてみてください。

なお今回、LuCI(WebUI)は依存モジュールとしてluci-compatluci-lua-runtimeを利用しますので、メニューコンフィグの該当項目に チェックを入れてください。

既にOpenWrtをデバイスにインストールした方

LuCIまたはコンソール上でluci-compatとluci-lua-runtimeを追加インストールしてください。 次はコンソール上でのインストール例です。

root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install luci-compat
root@OpenWrt:~# opkg install luci-lua-runtime <--- 正直、こちらは既にインストールされていると思います。

注意
OpenWrtデバイスのネットワーク接続が正常なのに上記のコマンド実行が失敗する場合、そのOpenWrtデバイス用のリポジトリに そもそもluci-compatやluci-lua-runtimeパッケージが存在しない可能性があります。その場合はluci-compatとluci-lua-runtimeをプリインストールしたOpenWrtファームウェアイメージを 作成する必要があります。

準備2:OpenWrtデバイスと開発用PCを接続してコンソールにログインする

OpenWrtデバイスを用意できたら、次の図のようにLANケーブルでOpenWrtデバイスと開発用PCを繋いでください。

※図ではRaspberry PiがOpenWrtデバイスです。読者の方は、ご自身が持つOpenWrtデバイスとして考えてみてください。

OpenWrtデバイス(192.168.1.1/24)が管理するデフォルトのLANネットワークは192.168.1.0/24であり、SSHサーバーのdropbearが起動するようになっています。

また、SSHログインに必要な初期パスワード設定は無しのため、以下のコマンドを実行することでログインが成功します。

kamo@kamo:~$ ssh 192.168.1.1 -l root

準備3:SCPコマンドによるファイル転送方法を理解する

次に、UbuntuやWindowsで利用可能なSCPコマンドについて使い方を押さえましょう。

SCPコマンドは作成したカスタムページ(HTMLファイルやLuaスクリプト)をOpenWrtデバイスに転送するために使用します。

OpenWrtをインストールしたばかりのデバイス条件では、次のようにSCPコマンドを実行することでファイル転送が可能です。

kamo@kamo:~$ touch test-file
kamo@kamo:~$ echo helloworld > test-file
kamo@kamo:~$ scp ./test-file root@192.168.1.1:/root

上記は、開発用PCのカレントディレクトリ内のtest-fileをOpenWrtデバイス(192.168.1.1/24)の/rootディレクトリに転送した例です

これだけ押さえておけば、ひとまず大丈夫かと思います。もっと知りたい方はこちらを参照してみてください。

準備4:LuCIの変数キャッシュ機能をOFFにする

カスタムページをファイル転送して動作確認をする際に、LuCIのキャッシュ機能が最新Webページの表示を邪魔します。

そのため、LuCIのキャッシュ機能を表すUCIオプション(luci.ccache.enable)の値をOFFを示す0に変更してください。

root@OpenWrt:~# uci set luci.ccache.enable=0
root@OpenWrt:~# uci commit luci
root@OpenWrt:~# reboot

本題:カスタムページの作成

その1:説明ページの作成と追加

最初に、静的なHTMLページを作成しましょう。

今回作成するHTMLページはこの記事で作成するカスタムページの説明文を掲載したものです。 そのため、先ずは開発PC上で、次のHTMLファイル(desc.htm)を作成してください。

※LuCI内の既存HTMLファイルに倣い拡張子を「.htm」としています。

-- desc.htm
<%+header%> <!-- themeのheader部が紐付きます。 ThemeとはUIデザインのことです。-->

<h1>[Desc] luci-app-sample01</h1>

<p>
    This is the description page for luci-app-sample01.<br>
    luci-app-sample01 combines the wi-fi, router and access point configuration interfaces introduced in <a href="https://utakamo.com/article/openwrt/beginner/intro03.html">my blog article</a> into a single page.
</p>

<p>
    <strong>[Implementation difficulty Lv.1]</strong><br>
    This section allows you to modify the parameters in the OpenWrt network and wireless configuration files (/etc/config/network and /etc/config/wireless).
    In other words, it is a LuCI plugin that only manipulates the configuration files managed by the standard OpenWrt application.
</p>

<h2>What this app can do</h2>

<ul>
    <li>Wi-Fi Settings</li>
    <li>Router Settings</li>
    <li>AP Settings</li>
    </ul>

<h2>Page Link</h2>

<ul>
    <li>[Desc] luci-app-sample01  /cgi-bin/luci/my/luci-app-sample01/desc (this page)</li>
    <li>Wi-Fi Setting (kamo custom)  <a href="../../../../cgi-bin/luci/admin/network/custom-page/wireless"><device ip address>/cgi-bin/luci/admin/network/custom-page/wireless"</a></li>
    <li>Network Setting (kamo custom)  <a href="../../../../cgi-bin/luci/admin/network/custom-page/network"><device ip address>/cgi-bin/luci/admin/network/custom-page/network"</a></li>
</ul>

<h2>Reference</h2>
<p>
    The following references provide an implementation; developers familiar with OpenWrt should look here.
</p>
<ul>
    <li>luci Wiki:  <a href="https://github.com/openwrt/luci/wiki">https://github.com/openwrt/luci/wiki</a></li>
</ul>

<%+footer%> <!-- themeのfooter部が紐付きます。 -->

HTMLファイルの作成が完了したら、次にカスタムページのルーティング設定を管理するコントローラースクリプトをLua言語で書いていきます。

Webサービスのルーティング設定とは?

昔ながらの方法で作成されたWebサイトは、ドキュメントルートのサーバー内ディレクトリを基点として、 そのディレクトリ内に配置した各ページの絶対パスがそのページにアクセスするためのURLになります。

しかし、それではURLを決定するために常にファイル(コンテンツ)の配置関係を意識しなくてはならず、コンテンツ管理の自由度が低いです。

そのため、近年ではルーティング設定を管理するスクリプトを通して、URLとコンテンツ配置の関係を分離した方法が取られています。

OpenWrtのLuCIでもルーティング設定が存在し、それが以下のLuaスクリプト(module.lua)になります。

desc.htmと同様に、開発PC上に次のmodule.luaを作成してください。

-- module.lua
module("luci.controller.luci-app-sample01.module", package.seeall)

function index()
    -- 以下の記述で、次のURLでアクセスできるようになります。<ip address ex) 192.168.1.1>/cgi-bin/luci/utakamo/luci-app-sample01/desc
    entry({"utakamo", "luci-app-sample01", "desc"}, template("luci-app-sample01/desc"), "desc", 20).dependent=false
end

上記に掲載した2つのファイル(desc.htmとmodule.lua)を開発PC上で作成できたら、OpenWrtデバイス環境にアップロードします。

アップロードの準備として、OpenWrt環境上にアップロード先のディレクトリを作成する必要があります。

今回、ルーティング設定を管理するLuaスクリプトはmodule("luci.controller.luci-app-sample01", package.seeall) という文でLuCIにモジュールを公開しています。

そのため、OpenWrtデバイスのコンソール上で次のコマンドを実行することで、module.luaを管理するディレクトリ(/usr/lib/lua/luci/controller/luci-app-sample01)を作成します。

root@OpenWrt:~# mkdir -p /usr/lib/lua/luci/controller/luci-app-sample01
root@OpenWrt:~# mkdir -p /usr/lib/lua/luci/view/luci-app-sample01

作成できたら、今度は開発用PCのコンソール上で次のSCPコマンドを実行して、desc.htmとmodule.luaをOpenWrtデバイスの以下にアップロードします。

kamo@kamo:~$ scp ./module.lua root@192.168.1.1:/usr/lib/lua/luci/controller/luci-app-sample01
kamo@kamo:~$ scp ./desc.htm root@192.168.1.1:/usr/lib/lua/luci/view/luci-app-sample01

アップロード完了後、開発PCのWebブラウザーを開いてURL入力欄に「http://192.168.1.1/cgi-bin/luci/utakamo/luci-app-sample01/desc」と打ち込んでみてください。 すると、次のカスタムページ(desc.htm)が表示されます。

カスタムページ(desc.htm)がLuCIに反映されない場合

desc.htmとmodule.luaが以下のOpenWrtのディレクトリパスに格納されているか確認してください。

  • /usr/lib/lua/luci/view/luci-app-sample01/desc.htm
  • /usr/lib/lua/luci/controller/luci-app-sample01/module.lua

上記パス通りであれば、原因はキャッシュかもしれません。OpenWrtデバイスを再起動してください。

それでも表示されない場合、依存モジュールのluci-compatとluci-lua-runtimeがインストールされているか確認してください。

その2:ネットワーク設定ページの作成と追加

次はネットワーク設定用のページを追加します。

先ず最初に、上記で作成したmodule.luaに対して、ネットワーク設定用ページ(network.lua)のルーティング設定を追記します。

-- module.lua
module("luci.controller.luci-app-sample01.module", package.seeall)

function index()
    entry({"utakamo", "luci-app-sample01", "desc"}, template("luci-app-sample01/desc"), "desc", 20).dependent=false
    -- 以下を追記する
    entry({"admin", "network", "custom-page", "network"}, cbi("luci-app-sample01/network"), "Network Setting (kamo custom)", 30).dependent=false
end

追記できたら、前回と同様に開発PCからOpenWrtデバイスに対してSCPコマンドを使用してmodule.luaをアップロードします。 ※OpenWrtデバイスに入っていた前回のmodule.luaは最新のmodule.luaに更新されます。

kamo@kamo:~$ scp ./module.lua root@192.168.1.1:/usr/lib/lua/luci/controller/luci-app-sample01

module.luaを更新できたら、次は肝心のネットワーク設定ページであるnetwork.luaを以下の内容で作成します。

-- network.lua

-- [execut_cmd function] https://utakamo.com/article/lua/execute-cmd-from-lua/index.html
execute_cmd = function(cmd)
    local handle = io.popen(cmd)
    local result = handle:read('*a')
    handle:close()
    
    return result
end

split = function(inputString, delimiter)
    local result = {}
    local pattern = string.format("([^%s]+)", delimiter)
    
    for match in inputString:gmatch(pattern) do
        table.insert(result, match)
    end
    
    return result
end

m = Map("network", "Network Setting (kamo custom)")

s = m:section(TypedSection, "interface")
s.addremove = true
function s:filter(value)
    return value ~= "loopback" and value
end 
s:depends("proto", "static")
s:depends("proto", "dhcp")

p = s:option(ListValue, "proto", "Protocol")
p:value("static", "static")
p:value("dhcp", "dhcp")
p.default = "static"

d = s:option(ListValue, "device", "Device")
active_ifs = execute_cmd("ip link show up | awk '/^[0-9]+: / {print substr($2, 1, length($2)-1)}'")
active_iflist = split(active_ifs, '\n')

for _, v in ipairs(active_iflist) do
    d:value(v, v)
end

s:option(Value, "ipaddr", "ip"):depends("proto", "static")

s:option(Value, "netmask", "Netmask"):depends("proto", "static")

return m

ソースコードの内容について

上記のnetwork.luaはLuCI用のCBI(Configuration Bind Interface)関数を利用して、 UCI(Unified Configuration Interface)とWebUIのインタフェースをバインドしています。

詳細については、表示される設定画面の各インタフェースの対応を比較するか、ChatGPTや Microsoft Copilotにでも聞いてみてください。

なお、システムが認識している優先・無線インタフェースを取得するために、以下の記事で紹介した 方法(execute_cmd関数)を使用しています。

Luaプログラミング Luaから外部ソフトウェアを実行して結果を取得する方法

興味があれば、参照してみてください。

network.luaを作成できたら、OpenWrtデバイスにアップロードします。

前回同様に、予めOpenWrtデバイス側でnetwork.luaを格納する以下のディレクトリを作成します。

root@OpenWrt:~# mkdir -p /usr/lib/lua/luci/model/cbi/luci-app-sample01

ディレクトリを作成できたら、開発PC上で作成したnetwork.luaをOpenWrtデバイス環境にアップロードします。

kamo@kamo:~$ scp ./network.lua root@192.168.1.1:/usr/lib/lua/luci/model/cbi/luci-app-sample01

アップロード完了後、開発PCのWebブラウザーを開いてURL入力欄に「http://192.168.1.1/cgi-bin/luci/admin/network/custom-page/network」と打ち込んでみてください。 すると、ログイン画面が表示されますので、必要であればUsernameとPasswordを入力します。(OpenWrtインストール直後はUsernameは「root」、パスワードは無し(入力しない)です。)

ログインが成功すると、自作のネットワーク設定ページ(network.lua)が表示されます。

その3:Wi-Fi設定ページの作成と追加

次はWi-Fi設定用のページを追加します。

先ず最初に、上記で作成したmodule.luaに対して、ネットワーク設定用ページ(wireless.lua)のルーティング設定を追記します。

-- module.lua
module("luci.controller.luci-app-sample01.module", package.seeall)

function index()
    entry({"utakamo", "luci-app-sample01", "desc"}, template("luci-app-sample01/desc"), "desc", 20).dependent=false

    -- 以下を2行を追記する
    entry({"admin", "network", "custom-page"}, firstchild(), "CUSTOM PAGE", 30).dependent=false
    entry({"admin", "network", "custom-page", "wireless"}, cbi("luci-app-sample01/wireless"), "Wi-Fi Setting (kamo custom)", 30).dependent=false

    entry({"admin", "network", "custom-page", "network"}, cbi("luci-app-sample01/network"), "Network Setting (kamo custom)", 30).dependent=false
end    

追記できたら、前回と同様に開発PCからOpenWrtデバイスに対してSCPコマンドを使用してmodule.luaをアップロードします。 ※OpenWrtデバイスに入っていた前回のmodule.luaは最新のmodule.luaに更新されます。

kamo@kamo:~$ scp ./module.lua root@192.168.1.1:/usr/lib/lua/luci/controller/luci-app-sample01

module.luaを更新できたら、次は肝心のWi-Fi設定ページであるwireless.luaを以下の内容で作成します。

-- wireless.lua

ubus = require("ubus")

-- [References] https://openwrt.org/docs/techref/ubus#lua_module_for_ubus
ubus_call = function(path, method, json_param)

    local conn = ubus.connect()

    if not conn then
        return
    end

    local result = conn:call(path, method, json_param)

    return result
end

m = Map("wireless", "Wi-Fi Setting (kamo custom)")
radio = m:section(TypedSection, "wifi-device")
radio.addremove = true
function radio:filter(value)
    return value
end 

wave = radio:option(ListValue, "disabled", "Wi-Fi Carrier Wave")
wave:value("1", "OFF")
wave:value("0", "ON")

country_code = radio:option(ListValue, "country", "COUNTRY")
wlan = ubus_call("iwinfo", "devices", {})

if #wlan.devices >= 1 then
    countrylist = ubus_call("iwinfo", "countrylist", {device = wlan.devices[1]})
    for _, item in ipairs(countrylist.results) do
        country_code:value(item.code, item.country)
    end
end

txpower = radio:option(ListValue, "txpower", "TXPOWER")

if #wlan.devices >= 1 then

    txpowerlist = ubus_call("iwinfo", "txpowerlist", {device = wlan.devices[1]})

    for _, result in ipairs(txpowerlist.results) do
	if (result.dbm >= 6) and (result.dbm <= 10) then
            txpower:value(result.mw, result.dbm .. "dBm")
        end
    end
end

default_radio = m:section(TypedSection, "wifi-iface")
default_radio.addremove = true
function default_radio:filter(value)
    return value
end

-- [References] https://openwrt.org/docs/guide-user/network/wifi/basic#common_options
encryption = default_radio:option(ListValue, "encryption", "ENCRYPTION")
encryption:value('none', 'no authentication')
encryption:value('psk+tkip+ccmp', 'WPA Personal (PSK)')
encryption:value('psk2', 'WPA2 Personal (PSK)')
encryption:value('sae', 'WPA3 Personal (SAE)')

default_radio:option(Value, "ssid", "SSID")
key = default_radio:option(Value, "key", "KEY")
key:depends("encryption", 'psk+tkip+ccmp')
key:depends("encryption", 'psk2')
key:depends("encryption", 'sae')
key.password = true

return m

wireless.luaを作成できたら、OpenWrtデバイスにアップロードします。

格納先であるOpenWrtデバイスのディレクトリは上記で既に作っていますので、次のSCPコマンドで開発PC上のwireless.luaをOpenWrtデバイスにアップロードします。

kamo@kamo:~$ scp ./wireless.lua root@192.168.1.1:/usr/lib/lua/luci/model/cbi/luci-app-sample01

これで、今回のカスタムページの作成と追加は完了です。

一連の動作を見てみます。

先ず、開発PCのWebブラウザーのURL入力覧に「http://192.168.1.1」と打ち込んでみてください。 ログイン画面が表示されますので、必要であればUsernameとPasswordを入力します。(OpenWrtインストール直後はUsernameは「root」、パスワードは無し(入力しない)です。)

すると、LuCIの管理画面トップが表示されますので、「Network」プルダウンメニューの中から「CUSTOM PAGE」をクリックします。

これで、今回作成したネットワーク設定ページが表示されます。

また、ネットワーク設定ページのタブからWi-Fi設定ページにも遷移できることが分かります。

これら設定ページの各項目は対象のUCIコンフィグレーションファイル(/etc/config/network、 /etc/config/wireless)と紐づいているため、UI上の項目を通した設定変更が反映されます。

配布方法①:セットアップスクリプトの作成と実行

今回作成したカスタムページは、特定のハードウェアに依存したものではありません。

そのため、配布方法②で紹介するようなインストールパッケージを用意しなくてもOpenWrtデバイスに適用することができます。

ハードウェアに依存しないモジュールを配布する場合はここで紹介する方法も便利です。

それでは作成していきましょう。

Webサーバーの配布用URL(リポジトリ)を決定する

前節で作成したカスタムページを配布するために、まずはサーバーを用意します。

私の場合、GitHubでソース管理をしていますので、GitHubをサーバー代わりにします。

GitHubのリポジトリを作成し、カスタムページ一式を収録したアーカイブファイルと それをダウンロードするスクリプト(セットアップスクリプト)をアップロードします。

今回、私は次のURLで示すGitHubリポジトリを用意しました。

https://github.com/utakamo/UtakamoStudyApps/raw/main/luci-plugin/luci-app-sample01/setup

このリポジトリに対して、次に説明するアーカイブファイルとセットアップスクリプトをアップロードしていきます。

アーカイブファイルの作成

最初に、前節で作成した各カスタムページ用のLuaスクリプトとHTMLファイルを次のディレクトリ・ファイル構成になるように整理します。

上記の構成からアーカイブファイルを作成します。親のディレクトリであるluci-app-sample01を指定して次のコマンドを実行します。

kamo@kamo:~$ tar zcvf luci-app-sample01.tar.gz ./luci-app-sample01
./luci-app-sample01/
./luci-app-sample01/files/luasrc/
./luci-app-sample01/files/luasrc/controller/
./luci-app-sample01/files/luasrc/controller/module.lua
./luci-app-sample01/files/luasrc/model/
./luci-app-sample01/files/luasrc/model/cbi/
./luci-app-sample01/files/luasrc/model/cbi/network.lua
./luci-app-sample01/files/luasrc/model/cbi/wireless.lua
./luci-app-sample01/files/luasrc/view/
./luci-app-sample01/files/luasrc/view/cbi/
./luci-app-sample01/files/luasrc/view/cbi/desc.htm

作成できたアーカイブファイル(luci-sample01-app.tar.gz)は次で説明するセットアップスクリプトを使って展開し、 その中のファイルとスクリプトをOpenWrtデバイスの環境に配置します。

このアーカイブファイルは上記で説明したGitHubリポジトリにアップロードします。

これによって、このアーカイブは以下のURLでダウンロードが可能になりました。

https://github.com/utakamo/UtakamoStudyApps/raw/main/luci-plugin/luci-app-sample01/setup/luci-app-sample01.tar.gz

セットアップスクリプト(setup-sample01)の作成

最後にセットアップスクリプトを作成します。

上記で作成したアーカイブファイルをOpenWrtデバイスのローカル環境にダウンロードして、 展開をした後に適切なディレクトリに各ファイルをコピーするだけの簡単なスクリプトです。

#/bin/sh
#setup-sample01

DOWNLOAD_URL="https://github.com/utakamo/UtakamoStudyApps/raw/main/luci-plugin/luci-app-sample01/setup/luci-app-sample01.tar.gz"
TEMP_ARCHIVE_1="/tmp/luci-app-sample01.tar.gz"
TEMP_ARCHIVE_2="/tmp/luci-app-sample01.tar"
EXTRACT_DIR="/tmp/luci-app-sample01"
SRC_DIR="$EXTRACT_DIR/files/luasrc"
DST_DIR="/usr/lib/lua/luci"

help() {
    echo "setup-sample01 install ... install luci-app-sample01"
    echo "setup-sample01 remove  ... remove luci-app-sample01"
}

delete_archive_file() {
    rm -rf "$TEMP_ARCHIVE_1"
    rm -rf "$TEMP_ARCHIVE_2"
    rm -rf "$EXTRACT_DIR"
}

remove_install_file() {
    rm -rf "$DST_DIR/view/luci-app-sample01/"
    rm -rf "$DST_DIR/model/cbi/luci-app-sample01"
    rm -rf "$DST_DIR/controller/luci-app-sample01/"
}

install() {

    if ! opkg list-installed luci-compat | grep -q "luci-compat"; then
        echo -n "Do you want to install the luci-compat package? (Y/N) :"
        read reply

        if [ "$reply" = "Y" ]; then
            opkg update
            if ! opkg install luci-compat; then
                exit 1
            fi
        else
            echo "luci-app-sample01 install cancelled."
            exit 1
        fi
    fi

    # download
    wget --no-check-certificate -O "$TEMP_ARCHIVE_1" "$DOWNLOAD_URL"
    if [ "$?" -ne 0 ]; then
        echo "Failed to download $DOWNLOAD_URL" >&2
        exit 1
    fi

    # expands
    mkdir -p "$EXTRACT_DIR"
    gunzip -c "$TEMP_ARCHIVE_1" | tar -x -C /tmp

    if [ "$?" -ne 0 ]; then
    echo "Failed to extract $TEMP_ARCHIVE_1" >&2
    delete_archive_file
        exit 1
    fi

    # remove previous file for reinstall
    remove_install_file

    # create dir and copy
    mkdir -p "$DST_DIR/view/luci-app-sample01"
    mkdir -p "$DST_DIR/model/cbi/luci-app-sample01"
    mkdir -p "$DST_DIR/controller/luci-app-sample01"

    cp -a "$SRC_DIR/view/cbi/desc.htm" "$DST_DIR/view/luci-app-sample01/"
    cp -a "$SRC_DIR/model/cbi/network.lua" "$DST_DIR/model/cbi/luci-app-sample01/"
    cp -a "$SRC_DIR/model/cbi/wireless.lua" "$DST_DIR/model/cbi/luci-app-sample01/"
    cp -a "$SRC_DIR/controller/module.lua" "$DST_DIR/controller/luci-app-sample01/"

    delete_archive_file
    echo "Setup completed successfully"

    echo -n "System Reboot? (Y/N) :"
    read reboot

    if [ "$reboot" = Y ]; then
        reboot
    fi
}

remove() {
    remove_install_file
    echo "Remove completed successfully"
}

if [ "$1" = "install" ]; then
        install
elif [ "$1" = "remove" ]; then
        remove
else
        help
fi    

このセットアップスクリプトも作成後は、リポジトリにアップロードします。

そして、アップロードすると今回は以下のURLでダウンロードできるようになりました。

https://raw.githubusercontent.com/utakamo/UtakamoStudyApps/main/luci-plugin/luci-app-sample01/setup/setup-sample01

後はユーザーに、このセットアップスクリプトの使用方法をアナウンスすればいいだけです。

ということで、次はこのスクリプトを使ってOpenWrtデバイスにカスタムページを適用してみます。

セットアップスクリプトを利用してOpenWrtデバイスにカスタムページを適用する

それでは、実際にセットアップスクリプトをリポジトリから入手してカスタムページを OpenWrtデバイスに適用してみます。

カスタムページを適用したいOpenWrtデバイスのコンソール上で次のコマンドを実行して、 セットアップスクリプト(setup-sample01)をダウンロードします。

root@OpenWrt~:# wget https://raw.githubusercontent.com/utakamo/UtakamoStudyApps/main/luci-plugin/luci-app-sample01/setup/setup-sample01

時刻情報の不一致で、SSL認証エラーが発生してダウンロードできない場合の対処

OpenWrtデバイス側でwgetコマンドを実行すると、SSL認証に失敗してダウンロードができない場合があります。 大体の原因は時刻情報なので、dateコマンドを実行して現在時刻が正しいか確認してください。

root@OpenWrt:~# date

システムが認識している現在時刻が正しくない場合は、次のコマンドでNTPサーバーから時刻を取得してください。

/etc/init.d/sysntpd restart

これでも、wgetコマンドでダウンロードできない場合は-no-check-certificateオプションを付けて実行してください。

wget --no-check-certificate https://raw.githubusercontent.com/utakamo/UtakamoStudyApps/main/luci-plugin/luci-app-sample01/setup/setup-sample01

次に、このセットアップスクリプトを実行します。

root@OpenWrt:~# chmod +x setup-sample01
root@OpenWrt~:# ./setup-sample01 install
Setup completed successfully
System Reboot? (Y/N) :Y

これでカスタムページの適用が完了しました。

システムブート後にカスタムページのURLにアクセスしてみると、正常にページが表示されることが確認できます。

注意:セットアップスクリプトが失敗する場合

次のエラーが発生する場合、今回のプラグインの依存パッケージである luci-compatパッケージがお使いのデバイス用リポジトリに存在しないことが考えられます。

Unknown package 'luci-compat'.
Collected errors:
* opkg_install_cmd: Cannot install package luci-compat.

OpenWrtは様々なデバイス上で動作するOSですが、ハードウェア依存のあるアプリケーションパッケージはそのハードウェア用にビルドしなければなりません。 お使いのデバイスによっては、パッケージのビルドとリポジトリの更新が間に合っていない、そもそも止まっているなどの理由で欲しいパッケージ の情報がopkg updateで入手できない場合があります。

そのため、どうしてもインストールしたいパッケージがある場合は、ユーザーご自身が用意したPC(Linux)にOpenWrtのbuildrootを 構築し、インストールしたいパッケージを指定してビルドする必要があります。

配布方法②:カスタムページのパッケージ作成とインストール

ここでは、作成したカスタムページ(LuaスクリプトとHTMLファイル)を配布するために、 luci-app-sample01という名前でパッケージを作成してみます。

この方法では、パッケージ自体が特定のハードウェア用(今回の場合はターゲットのRaspberry Pi3B)のものになるので、 配布のしにくさがデメリットです。

逆にパッケージマネージャーソフトのopkgで管理できることがメリットになりますが、 今回はハードウェアに依存するようなバイナリファイルはありませんので、そのメリットは薄いです。

※全てのターゲットシステム(ハードウェア)をインストール対象にするパッケージが作成できましたので 上記の説明は取り消します。

それでは行ってみます。

この記事の内容で作成したluci-app-sample01パッケージ

ちなみに、この記事の内容で作成したパッケージは私のGitHub上で管理されていますので、 次のコマンド実行でインストール可能です。

root@OpenWrt:~# wget --no-check-certificate https://github.com/utakamo/UtakamoStudyApps/raw/refs/heads/main/luci-plugin/luci-app-sample01/setup/luci-app-sample01_1.0-r1_all.ipk
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install luci-app-sample01_1.0-r1_all.ipk

インストール完了後に再起動すれば、プラグインが利用可能になります。

手順1:パッケージ作成のためのディレクトリ管理

最初に、前節で作成した各カスタムページ用のLuaスクリプトとHTMLファイルを次のディレクトリ・ファイル構成になるように整理します。 ※以下の図にあるMakefileは次節で作成します。

上図のディレクトリ構成で各カスタムページが管理されているものとして、 パッケージ作成用のMakefileを記述します。

手順2:パッケージ用Makefileの作成

手順1で作成したUtakamoStudyApps/luci-plugin/luci-app-sample01直下に次のMakefileを作成します。

#~/UtakamoStudyApps/luci-plugin/luci-app-sample01/Makefile
include $(TOPDIR)/rules.mk

PKG_NAME:=luci-app-sample01
PKG_VERSION:=1.0
PKG_RELEASE:=1

SOURCE_DIR:=./files/luasrc

LUA_LIBRARYDIR = /usr/lib/lua
LUCI_LIBRARYDIR = $(LUA_LIBRARYDIR)/luci
LUCI_MODULEDIR = $(LUCI_LIBRARYDIR)/controller
LUCI_MODELDIR = $(LUCI_LIBRARYDIR)/model/cbi
LUCI_VIEWDIR = $(LUCI_LIBRARYDIR)/view

include $(INCLUDE_DIR)/package.mk

define Package/luci-app-sample01
    CATEGORY:=utakamo
    SECTION:=utakamo
    TITLE:=luci sample application
    DEPENDS:=+luci-compat
    PKGARCH:=all
endef

define Build/Compile
endef

define Package/luci-app-sample01/install
	$(INSTALL_DIR) $(1)$(LUCI_MODULEDIR)/luci-app-sample01
	$(INSTALL_DATA) $(SOURCE_DIR)/controller/module.lua $(1)$(LUCI_MODULEDIR)/luci-app-sample01
	$(INSTALL_DIR) $(1)$(LUCI_MODELDIR)/luci-app-sample01
	$(INSTALL_DATA) $(SOURCE_DIR)/model/cbi/network.lua $(1)$(LUCI_MODELDIR)/luci-app-sample01
	$(INSTALL_DATA) $(SOURCE_DIR)/model/cbi/wireless.lua $(1)$(LUCI_MODELDIR)/luci-app-sample01
	$(INSTALL_DIR) $(1)$(LUCI_VIEWDIR)/luci-app-sample01
	$(INSTALL_DATA) $(SOURCE_DIR)/view/cbi/desc.htm $(1)$(LUCI_VIEWDIR)/luci-app-sample01
endef

$(eval $(call BuildPackage,luci-app-sample01))

今回のMakefileはコンパイルが必要なソースファイルは含みませんので、define Build/Compileの文は空の状態です。 (もし、コンパイルしなければ行けないC言語ソースファイルがある場合はターゲットシステムを全て(all)にしてパッケージを作成することは得策ではありません。)

define Package/luci-app-sample01/installの文は、各ファイルをインストール先のOpenWrtデバイス環境と対になったパッケージ環境($(1) にコピーする処理です。

これで手順1で各ディレクトリの中に格納したHTMLファイルやLuaスクリプトが、$(1)で示されるパッケージ環境 の各ディレクトリ階層に配置されるようになります。

パッケージのターゲットシステム(ハードウェア)を全て(all)にするには?

今回作成するパッケージの中身はスクリプトのため、特定のアーキテクチャやシステム、ハードウェアに依存したものではありません。 そのため、パッケージのターゲットシステムをallにしておいた方が便利です。

パッケージのターゲットシステムをallにするには、上記Makefileに記載されている通り、PKGARCH:=all の文を記述します。

パッケージのビルド

次は、上記で作成したパッケージ作成用のMakefileをOpenWrtのbuildrootにfeed情報としてインストールしてビルドします。

まず、カスタムページ(Luaスクリプト・HTMLファイル)とMakefileを格納したディレクトリの親ディレクトリである UtakamoStudyAppsまでのフルパスを~/openwrt/feeds.confの内容として以下のように記述します。

kamo@kamo:~/openwrt$ nano feeds.conf
# feeds.confの記載例
# パッケージパスはご自身の環境を確認の上、記述してください
# src-link [フィード名] [パッケージパス]
src-link utakamo /home/kamo/UtakamoStudyApps

次に、以下のコマンドでbuildrootでカスタムページのMakefileを読み込ませ、utakamo feed情報としてインストールします。

kamo@kamo:~/openwrt$./scripts/feeds update -a
kamo@kamo:~/openwrt$./scripts/feeds install -a -p utakamo

正常にfeed情報がインストールできたら、make menuconfigで設定画面を表示します。

kamo@kamo:~/openwrt$ make menuconfig

一覧の中に「utakamo」という項目がありますので、これを選択します。

すると、「utakamo」の中に今回作成したLuCIのカスタムページ用パッケージの「luci-app-sample01」がありますので、これにチェックを入れます。

その後、変更を保存して設定画面(メニューコンフィグ)を終了します。※Exitを選択すると、終了する前に変更内容を保存するか聞かれますので「YES」を選んでください。

コンソールに操作が戻ってきますので、次のMakeコマンドでluci-app-sample01パッケージを作成してください。

kamo@kamo:~/openwrt$ make package/luci-app-sample01/compile

正常にパッケージ作成が完了すると、openwrt/bin/packages/<chip name>/utakamoに作成されたパッケージ本体(.ipk)があります。 ※今回は全ターゲットシステム向けのパッケージを作成しましたので、luci-app-sample01_1.0-r1_all.ipkという名前でパッケージが出来上がりました。

kamo@kamo:~/openwrt/bin/packages/aarch64_cortex-a53/utakamo$ ls
Packages                                        Packages.gz                                    
Packages.manifest                               Packages.sig                                           
index.json                                      luci-app-sample01_1.0-r1_all.ipk

これでパッケージ作成は完了です。

パッケージのインストール

上記で作成したパッケージをOpenWrtデバイスに転送してインストールします。

SCPコマンドを次のように使用することで、はOpenWrtデバイスに転送できます。 ※もちろん、いろいろなファイル転送のいづれかで作成したパッケージをOpenWrtデバイス環境に転送することができればOKです。

kamo@kamo:~/openwrt/bin/packages/aarch64_cortex-a53/utakamo$ scp ./luci-app-sample01_1.0-r1_all.ipk root@192.168.1.1:/root

OpenWrtデバイスにパッケージを転送後、OpenWrtのコンソール上で次のコマンドを実行することでインストールします。

root@OpenWrt~:# opkg update <--- luci-app-sample01はluci-compatを依存パッケージとするので、その情報を確実にシステム認識させるためにupdateを実行します。
root@OpenWrt~:# opkg install luci-app-sample01_1.0-r1_all.ipk

以上で、LuCI用カスタムページの作成と追加からパッケージ作成について紹介しました。

注意:luci-app-sample01パッケージのインストールに失敗する場合

次のエラーが発生する場合、今回のサンプルパッケージ(luci-app-sample01)の依存パッケージである luci-compatパッケージがお使いのデバイス用リポジトリに存在しないことが考えられます。

root@OpenWrt:~# opkg install luci-app-sample01_1.0-r1_all.ipk
Unknown package 'luci-app-sample01'.
Collected errors:
    * pkg_hash_check_unresolved: cannot find dependency luci-compat for luci-app-sample01
    * pkg_hash_fetch_best_installation_candidate: Packages for luci-app-sample01 found, but incompatible with the architectures configured
    * opkg_install_cmd: Cannot install package luci-app-sample01.

OpenWrtは様々なデバイス上で動作するOSですが、ハードウェア依存のあるアプリケーションパッケージはそのハードウェア用にビルドしなければなりません。 お使いのデバイスによっては、パッケージのビルドとリポジトリの更新が間に合っていない、そもそも止まっているなどの理由で欲しいパッケージ の情報がopkg updateで入手できない場合があります。

そのため、どうしてもインストールしたいパッケージがある場合は、ユーザーご自身が用意したPC(Linux)にOpenWrtのbuildrootを 構築し、インストールしたいパッケージを指定してビルドする必要があります。

LuCIがシステム内部で利用するUCI操作プロセス(/sbin/rpcd)について

LuCIのページ上でユーザーに提供する設定項目の大半は、OpenWrtアプリケーションの環境変数であるUCIコンフィグレーションファイルと紐づいています。

例えば、ユーザーがLuCIのネットワーク設定ページからIPアドレスなどを変更すると、対応するUCIコンフィグレーションファイル(例:/etc/config/network)に変更内容が書き込まれ、 そのコンフィグレーションファイルを環境変数とするOpenWrtアプリケーション(例:netifd)が現在の設定を読み込み直し、ユーザーによる設定変更がシステムに適用されます。

UCIコンフィグレーションファイルに対する直接的な操作はUCIコマンド(/usr/bin/uci)がサポートします。ユーザー自身がOpenWrtデバイスの コンソール画面からroot@OpenWrt:~# uci set network.lan.ipaddr=192.168.2.1などのように実行することでも 設定に変更を加えることが可能です。

UCIコマンド(/usr/bin/uci)の詳細について

以下の記事で、内部の仕組みや使い方について紹介しています。この節の説明についてより深い理解を得たい方、興味がある方は参考にしていただければと思います。

そのため、コンソール上でUCIコマンドを利用して設定内容を頻繁に変更している人から見ると、 「LuCIもユーザーによる設定変更を受けたら、内部でUCIコマンド(/usr/bin/uci)を使ってUCIコンフィグレーションファイルを操作している」と思うかもしれません。

しかし実際には、LuCIはこのUCIコマンド(/usr/bin/uci)をシステム内部で利用していません。

LuCIがシステム内部で利用しているのは、UCIコマンド(/usr/bin/uci)と同等な機能を持ち、UBUSによるプロセス間通信とセッション 管理機能を持つRPC対応版のUCIコマンド(/sbin/rpcd)です。

このRPC対応版のUCIコマンド(/sbin/rpcd)は、いつ来るか分からないUCI操作リクエストのためにシステムブート時から稼働しているUBUS対応アプリケーション(daemon)でもあります。

RPC対応版UCIコマンドのクライアントはネットワークを通して繋がり、LuCIによってUI提供されているユーザーPC(ホスト)です。そのため、同時に複数台のクライアントがLuCIによる設定ページを 通してUCI操作をすることを想定して、セッション管理をサポートしています。

詳細な動作については以下の通りです。

LuCIが使用するRPC対応版UCIコマンドの仕組み(処理の流れ)

ここでは、2台のクライアントデバイス(PC)がLuCIのWebUIにアクセスして、それぞれがIPの設定変更をしたと仮定します。

このとき、それぞれのクライアントデバイスからIPアドレスの設定変更要求を受けたLuCIは、UCI操作プロセスの/sbin/rpcd に対して、UBUS経由で設定変更を通知します。

※LuCIは内部でubus call uci set '{"config":"network","section":"wan","type":"interface","match":"","values":{"ipaddr":"192.168.3.1"},"ubus_rpc_session":<session>}' などのUBUSコマンドを実行しています。

このとき、各デバイスのIP設定の変更は、それらデバイスのセッション番号に紐づいたステージング領域(RAM)に一時保存されます。

セッション番号に紐づいたステージング領域は、/sbin/rpcdによって割り当てられたメモリ領域(/tmp/run/rpcd/uci-<ubus_rpc_session>)です。

上図では、2台のクライアントデバイスの各セッション(Session A、Session B)ごとにステージング領域(①、②)が 動的に作成され、その中に変更内容である192.168.3.1192.168.2.1が格納されています。

ステージング領域にある任意のIP設定はUCIオブジェクトのapplyメソッドを利用することでコンフィグ(今回では/etc/config/network)にマージすることができます。 これにより、対応アプリケーションの環境変数として使用できます。

上図では、Session Aの変更内容である192.168.3.1をapplyメソッドの実行によって適用しています。 applyメソッドは、タイムアウト時間内にネットワーク通信が確認できない場合、元の設定状態にロールバックできます。

ネットワーク通信が確認できた場合は、設定変更を確定します。

これにより、設定変更がフラッシュ領域のコンフィグ(今回の場合は/etc/config/network)に書き込まれます。

このように、LuCI経由で実施される設定変更はUBUSとUCI操作プロセスにより、実現されます。

【おまけ】ユーザーPCとLuCIのUBUS通信を見てみる

上記で説明したLuCIとユーザーPCのやり取りの一部はUBUSのmonitorコマンドで見ることができます。

確認するには、OpenWrtデバイス側のコンソールで次のUBUSコマンドを実行します。

root@OpenWrt:~# ubus monitor

後は、LuCIを通して何らかの設定を変更すれば、その時の通信内容(UBUSへのリクエスト含む)がログとして出力されます。

次はネットワークのIPアドレス設定を192.168.1.1から192.168.2.1に変更したときのUBUS monitorコマンドのログ出力の抜粋です。 (IPアドレスの設定項目に192.168.2.1を入力して、SAVEボタンを押した時の内容になります。)

root@OpenWrt:~# ubus monitor

...省略...

: {"objpath":"uci"}
-> ce806aa2 #00000000           data: {"objpath":"uci","objid":2098094064,"objtype":208355174,"signature":{"configs":{},"get":{"config":3,"section":3,"option":3,"type":3,"match":2,"ubus_rpc_session":3},"state":{"config":3,"section":3,"option":3,"type":3,"match":2,"ubus_rpc_session":3},"add":{"config":3,"type":3,"name":3,"values":2,"ubus_rpc_session":3},"set":{"config":3,"section":3,"type":3,"match":2,"values":2,"ubus_rpc_session":3},"delete":{"config":3,"section":3,"type":3,"match":2,"option":3,"options":1,"ubus_rpc_session":3},"rename":{"config":3,"section":3,"option":3,"name":3,"ubus_rpc_session":3},"order":{"config":3,"sections":1,"ubus_rpc_session":3},"changes":{"config":3,"ubus_rpc_session":3},"revert":{"config":3,"ubus_rpc_session":3},"commit":{"config":3,"ubus_rpc_session":3},"apply":{"rollback":7,"timeout":5,"ubus_rpc_session":3},"confirm":{"ubus_rpc_session":3},"rollback":{"ubus_rpc_session":3},"reload_config":{}}}
-> ce806aa2 #00000000         status: {"status":0}
#以下のログからUCIオブジェクトのsetメソッドがセッション番号[12f8439dc10ee9d90a81180472116bfa]に使用されたことが分かる
<- ce806aa2 #7d0e5ff0         invoke: {"objid":2098094064,"method":"set","data":{"ubus_rpc_session":"12f8439dc10ee9d90a81180472116bfa","config":"network","values":{"ipaddr":"192.168.2.1"},"section":"lan"}}
-> b7d95343 #ce806aa2         invoke: {"objid":2098094064,"method":"set","data":{"ubus_rpc_session":"12f8439dc10ee9d90a81180472116bfa","config":"network","values":{"ipaddr":"192.168.2.1"},"section":"lan"},"user":"root","group":"root"}
<- b7d95343 #ce806aa2         status: {"status":0,"objid":2098094064}
-> ce806aa2 #7d0e5ff0         status: {"status":0,"objid":2098094064}

...省略...

このログを見ると、LuCIがUCI操作プロセス(/sbin/rpcd)を利用してUCIコンフィグレーションファイルに対する操作をしていることが分かります。

上記の内容では、セッション番号[12f8439dc10ee9d90a81180472116bfa]によるnetworkコンフィグへの変更(192.168.2.1)がUCIオブジェクトのsetメソッドによって実施されたことが分かります。

setメソッドはステージング領域に変更情報を書き込む機能なので、セッション番号に紐づいた動的メモリ内に変更差分が記録されたことが推測できます。

このことを裏付けるために、ステージング領域の内容を見るchangesメソッドを実行してみます。

root@OpenWrt:~# ubus call uci changes '{"config":"network", "ubus_rpc_session":"12f8439dc10ee9d90a81180472116bfa"}'
{
        "changes": [
                [
                        "set",
                        "lan",
                        "ipaddr",
                        "192.168.2.1"
                ]
        ]
}

上記から、セッション番号に紐づいたステージング領域にIPアドレスの変更が入っていることが確認できます。

【おまけ】Luaスクリプト用UBUSモジュールでカントリコード一覧を取得する方法

UBUSによるプロセス間通信を利用して、iwinfoオブジェクトからカントリーコードを取得するLuaスクリプトを掲載します。

これは、カスタムページのWi-Fi設定画面(wireless.lua)を作る際にできたテストコードです。もったいないので掲載することにしました。

この処理はカントリコード用のプルダウンメニューに各コードに対応する国名を表示するために利用しています。

OpenWrtデバイス上のどこでも良いので、以下のスクリプトを置いて実行してみると国情報の一覧が取得できます。

-- country.lua
ubus  = require("ubus")

ubus_call = function(path, method, json_param)

    local conn = ubus.connect()

    if not conn then
        return
    end

    local result = conn:call(path, method, json_param)

    return result
end

wlan = ubus_call("iwinfo", "devices", {})

for _, value in ipairs(wlan.devices) do
    print("--->" .. value)
end

countrylist = {}

if #wlan.devices >= 1 then
    countrylist = ubus_call("iwinfo", "countrylist", {device = wlan.devices[1]})
else
    return
end

print("wlan.devices length = " .. #wlan.devices)
print("wlan.devies[1] = " .. wlan.devices[1])
print("countrylist length = " .. #countrylist)

for _, item in ipairs(countrylist.results) do
    print(item.country .. ' ---> ' .. item.code)
end

上記のLuaスクリプトは、OpenWrtのコンソール上で次のUBUSコマンドを実行したときと同等の結果になります。

# 例)interface : wlan0で国情報を取得する例
root@OpenWrt:~# ubus call iwinfo countrylist '{"device":"wlan0"}'
# 引数のWLANインタフェースはシステムが認識しているものである必要があります。
# 次のUBUSコマンドでWLANインタフェースの一覧が取得できます。
# root@OpenWrt:~# ubus call iwinfo devices

このように、UBUSを使用してWi-Fiドライバーにカントリーコードとして解釈できる設定値の組を教えてもらうことにより、 Wi-Fiチップ&ドライバーの差異をLuCIのWebUI層で抽象化することができます。

ただし、このような情報を提供してくれるかはハードウェア・ドライバーのサポートに依存します。

おわりに

今回は、LuCI用のカスタムページを作成して追加する方法を紹介しました。

この記事の内容で、UIを自分好みのものにカスタマイズできるようになります。

しかし、より深くアプリケーション開発をしたいと考えると、今回の知識では物足りません。

自作ソフトウェア(スクリプト含む)とLuCIとのプロセス間通信(IPC/RPC)も押さえておく必要があります。

さらに深い知識を理解して、OpenWrtのアプリケーション開発をしたい方は以下の記事をご参考ください。

  1. OpenWrtアプリケーション開発 自作スクリプトと連携するLuCI用プラグインの作成と追加(難易度Lv2)
  2. OpenWrtアプリケーション開発 自作UBUS対応アプリと連携するLuCI用プラグインの作成と追加(難易度Lv3)(作成中)

参考文献