2017年1月29日 星期日

LinkIt 7688 筆記: rpcd

LinkIt 7688 Web CGI 入門(2) 一文提到,要與 7688 打交道,很難不碰到 ubus,那要怎麼加入 ubus 這個大家庭呢?

筆者所知有兩條途徑
libubox 是 OpenWrt 12.9 之後加入的基本程式庫,前面提到的 uHTTPd、netifd 就是奠基在此之上,如果你想用 C/C++ 開發(也有 Lua binding),想要有自己的事件迴圈,追求效能極大化,那你就要了解他。

rpcd 也是建立在  libubox 上,rpcd 之於 ubus 就好比 xinetd 之於 internet,在筆者另一篇文章「Raspberry Pi 筆記: 解決嵌入式系統列印困境」也有提及 xinetd。簡單來說您可以把你的程式掛在 rpcd 之下(稱為 plugin),當 rpcd 收到遠端呼叫(Remote Procedure Call)時(例如前文提到的 jsonrpc),就可以喚起你的 plugin:


接下來看如何開發 plugin。

1. 編寫 plugin script

基本上只要 7688 內能讀寫 stdin/stdout 的程式語言都行,無論是 shell script, Python, Lua, Node...

以 Lua 為例(myrpc):
#!/usr/bin/lua
-- /usr/libexec/rpcd/myprc
require "luci.json"

local json = luci.json
if arg[1] == "list" then
    io.write(json.encode({echo = {arg1 = "str"}}))
elseif arg[1] == "call" and arg[2] == "echo" then
    local echo_arg = io.read("*all")
    local echo_decoded_arg = json.decode(echo_arg)
    if echo_decoded_arg.arg1 == nil or type(echo_decoded_arg.arg1) ~= "string" then
        io.write(json.encode({ret1 = "", ret2 = "invalid argument"}))
    else
        io.write(json.encode({ret1 = echo_decoded_arg.arg1}))
    end
end
Line 3 取得 luci JSON 編解碼模組。

Line 6-7 列出此 plugin 支援哪些 function 與參數型態。

Line 8-16 則是 plugin 被呼叫時,讀入參數 -> 進行運算 -> 回傳結果的區塊。myrpc 的動作很簡單,只是把參數 arg1 原封不動回傳而已(Line 14)。

如果到這裡還是看不懂沒關係,等等實際執行就一目了然了。

2. 測試 plugin

將 myrpc 拷貝到 /usr/libexec/rpcd,然後輸入以下指令:


檢查是否有成功載入:


從這裡可以看到 arg1 參數型態為 String

呼叫測試:


這裡附上 Python 版,因本人非 Python 專家,難免貽笑大方,還請各位見諒 m(_ _)m
#!/usr/bin/python
#/usr/libexec/rpcd/myprc2

import sys
import json

arg = sys.argv

if len(arg) == 2 and arg[1] == "list":
    sys.stdout.write(json.dumps({'echo': {'arg1':'str'}}))
elif len(arg) == 3 and arg[1] == "call" and arg[2] == "echo":
    echo_arg = json.loads(sys.stdin.read())
    if 'arg1' not in echo_arg or echo_arg['arg1'] == None:
        sys.stdout.write(json.dumps({"ret1" : "", "ret2" : "invalid argument"}))
    else:
        sys.stdout.write(json.dumps({"ret1" : echo_arg['arg1']}))
    

3. jsonrpc 測試

同樣拿 rpc_demo.html 進行修改,首先增加一些標籤:
<h3> Echo test <⁄h3>
Send <input type="input" id="EchoSend">
Receive <input type="input" id="EchoRecv">
<input type="button" value="Echo" id="BtnEcho">
<br><br>
增加 rpc_echo()
function rpc_echo(){
    config = { "jsonrpc": "2.0", "id": id++, "method": "call",
               "params": [ session, "myrpc", "echo", { "arg1": $("#EchoSend").val() } ] };
               
    do_ajax(config, function(reply) {
  if (!reply.result || reply.result[0] != 0){
            alert("failed to echo");
        }else{
            $('#EchoRecv').val(reply.result[1].ret1);
        }
    });
}
繫結 rpc_echo() 與 button tag:
$( document ).ready(function() {
    $("#BtnLogin").click(function() { rpc_login("root", $("#Password").val()); });
    $("#BtnLogout").click(function() { rpc_logout(); });
    $("#BtnMode").click(function() { rpc_wifi_mode($("#Mode").val()); });
    //......
    $("#BtnEcho").click(function() { rpc_echo(); });
});
修改 rpc_login(),增加存取 myprc 權限:
function rpc_login(user, pass)
{
    //......
 do_ajax(login, function(reply) {
        //......
  session = reply.result[1].ubus_rpc_session;
  var grant = { "jsonrpc": "2.0", "id": id++, "method": "call",
    "params": [ session, "session", "grant",
     { "scope": "uci", "objects": [ [ "*", "read" ], [ "*", "write" ] ] }
    ]};
                
  do_ajax(grant, function(reply) {
   if (!reply.result || reply.result[0] != 0) {
    alert("failed to grant object permissions");
    return;
   }
  });
        
  var grant2 = { "jsonrpc": "2.0", "id": id++, "method": "call",
    "params": [ session, "session", "grant",
     { "scope": "ubus", "objects": [ [ "myrpc", "echo" ] ] }
    ]};
  do_ajax(grant2, function(reply) {
   if (!reply.result || reply.result[0] != 0) {
    alert("failed to grant2 object permissions");
    return;
   }
   $("#Session").val(session);
   $("#fwSession").val(session);
   rpc_board_load();
   rpc_system_load();
   rpc_wifi_load();
            //...
  });        
        
 });
}
測試結果:

範例下載

沒有留言:

張貼留言