うたカモ技術ブログ

Linux OpenWrt

OpenWrt   サンプルプログラム掲載! UBUS用C言語ライブラリ-libubus(listenコマンド実装)

post:     update: 

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

今回は、自作アプリケーションにUBUSのlistenコマンドと同等な処理を実装る方法について紹介します。

UBUSのlistenコマンドはUBUS上でやり取りされるイベント(JSON)をリッスンする処理です。

イベントのリッスン機能を実装すれば、他のプロセスと協調して特定処理を実行することが可能です。 (もちろん、実装に依存するところではありますが)

今回は、そんなUBUSのlistenコマンドと同等な処理のC言語実装について紹介します。

UBUS学習の参考サイト紹介
UBUSの学習に役立つサイトを紹介します。以下のサイトを読むことでコマンドの使用方法だけでなく、その実装方法についても理解できます。 私の記事と併用して読むとより理解に繋がると思います。

  1. What is UBUS?
  2. OpenWRT UBUS mechanism

目次

サンプルプログラム(ubus-sample02)のダウンロード

今回取り上げるubus-sample02のソースは私のGitHubリポジトリ(UtakamoStudyApps)にあります。

読者ご自身の環境で本アプリをビルド・インストールしてみたい方は私のGitHubリポジトリをダウンロードするか、 次のようにクローンしてください。

OpenWrt:~# git clone https://github.com/utakamo/UtakamoStudyApps.git

なお、ビルド方法に関してはGitHubのリポジトリ説明を参照するか、以下の記事をご覧ください。
【第 6 回】OpenWrt開発入門 自作アプリのパッケージ作成とインストール - パッケージ作成手順

【ラズパイ限定】このサイトからパッケージを入手してインストールする。

このサイトでは、ラズベリーパイ限定でパッケージを配布しています。 シリーズ別のパッケージを以下に掲載しますので、インストールしてみてください。

モデル パッケージ ハッシュ(SHA256)
Raspberry Pi1 ubus-sample02_1.0-1_arm_arm1176jzf-s_vfp.ipk 34811e5ac41c297a6266d7007d972ea7097fe83df0d0f3c1db062494f10c60b4
Raspberry Pi2 ubus-sample02_1.0-1_arm_cortex-a7_neon-vfpv4.ipk 91eb648aa3959c1e3b0963bd472baf8bd7e417ce4857693e2114ae38b879687a
Raspberry Pi3 ubus-sample02_1.0-1_aarch64_cortex-a53.ipk d45d787795df485c7611544835fcd6bfb08d9a83e5b3b31edbe0b62bbc06ac3a
Raspberry Pi4 ubus-sample02_1.0-1_aarch64_cortex-a72.ipk c71af3cee5d619ae1db0c5e65c5bb521ad835d7961359e2dfd179433a37427a5

次のコマンドを実行すると、パッケージをインストールすることができます。(以下はRaspberry Pi3用パッケージのインストール例です。)

root@OpenWrt:~# wget https://utakamo.com/repo/openwrt/raspi3/ubus/ubus-sample02_1.0-1_aarch64_cortex-a53.ipk
root@OpenWrt:~# opkg install ubus-sample02_1.0-1_aarch64_cortex-a53.ipk

ソースコードの紹介

ubus-sample02の初期化スクリプト

このサイトで紹介している他のサンプルプログラムと同じ内容です。

ubus-sample02はprocdによってシステムブート時に優先順位99番目(START=99)に起動され、シャットダウン時に 優先順位10番目(STOP=10)で終了するように設定しています。

#!/bin/sh /etc/rc.common

USE_PROCD=1
START=99
STOP=10

start_service() {
    echo 'ubus-sample02 start'
    procd_open_instance 'ubus-sample02'
    procd_set_param command /usr/bin/ubus-sample02
    procd_close_instance
}

ubus-sample02のソースコード

今回はubus-sample02と命名したサンプルプログラムにlistenコマンドと同等な処理を実装しました。

UBUSの各コマンド実装は、libubus.hに記述されていますので、これを利用して以下のように listenコマンドの処理をする関数(reply_sample_event)を定義しました。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libubus.h>
#include <libubox/uloop.h>
#include <libubox/blobmsg_json.h>

enum {
    TEST_MESSAGE,
    TEST_MAX
};

static const struct blobmsg_policy test_policy[] = {
    [TEST_MESSAGE] = { .name = "message", .type = BLOBMSG_TYPE_STRING },
};

void ubus_process(void);
static void ubus_sample_handle_signal(int signo);
static void ubus_sample_setup_signals(void);

static int reply_cnt;
static void reply_sample_event(struct ubus_context *, struct ubus_event_handler *, const char *, struct blob_attr *);

struct ubus_event_handler ev = {
    .cb = reply_sample_event,
};

int main () {
    ubus_sample_setup_signals();
    ubus_process();
    return 0;
}

void ubus_process(void) {
    uloop_init();
    struct ubus_context *ctx = ubus_connect(NULL);
    ubus_add_uloop(ctx);
    ubus_register_event_handler(ctx, &ev, "sample-event");
    uloop_run();
    uloop_done();
}

static void ubus_sample_handle_signal(int signo)
{
    uloop_end();
}

static void ubus_sample_setup_signals(void)
{
    struct sigaction s;

    memset(&s, 0, sizeof(s));
    s.sa_handler = ubus_sample_handle_signal;
    s.sa_flags = 0;
    sigaction(SIGTERM, &s, NULL);
}

static void reply_sample_event(struct ubus_context *ctx, struct ubus_event_handler *ev,
                      const char *method, struct blob_attr *msg) {

    struct blob_attr *tb[TEST_MESSAGE];
    blobmsg_parse(test_policy, TEST_MAX, tb, blob_data(msg), blob_len(msg));

    if (!tb[TEST_MESSAGE]) {
            return;
    }

    char *message = blobmsg_get_string(tb[TEST_MESSAGE]);

    FILE *fp = NULL;
    mkdir("/tmp/ubus-sample02", 0755);

    if ((fp = fopen("/tmp/ubus-sample02/reply", "a")) == NULL) {
            return;
    }

    reply_cnt++;

    fprintf(fp, "[count:%d], message = %s\n", reply_cnt, message);

    fclose(fp);
}

このubus-sample02はイベントのsample-eventをリッスンします。

sample-eventがubusdから通知されると、sample-eventが持つJSONのmessageの 内容を一時ファイルの/tmp/ubus-sample02/replyに描きだします。

受け取ったsample-eventのJSONを受け取り、その内容を一時ファイルに出力する処理がreply_sample_eventです。

reply_sample_eventは受け取ったsample-eventがメッセージデータ場合に限り反応します。 つまり、他のプロセスが次のようにイベントを発行しない限り、ubus-sample02は結果を一時ファイルに出力しません。

ubus send sample-event '{"message":"Hello"}'

reply_sample_eventはイベント受信時にコールバックされるように、ubus-sample02起動時にubus_register_event_handler関数によって 登録する必要があります。

実行結果

ubus-sample02を実行すると、sample-eventの受信待ちとなります。
そのため、UBUSのsendコマンドで以下のようにsample-eventを送信します。

root@OpenWrt:~# ubus send sample-event '{"message":"Good Morning!"}'
root@OpenWrt:~# ubus send sample-event '{"message":"Hello!"}'
root@OpenWrt:~# ubus send sample-event '{"message":"Good Bye!"}'

ubus-sample02がsample-eventを受信すると、イベントの受信回数と共にメッセージ内容を/tmp/ubus-sample02/replyに出力します。

root@OpenWrt:~# cat /tmp/ubus-sample02/reply
[count:1], message = Good Morning!
[count:2], message = Hello!
[count:3], message = Good Bye!

おわりに

今回はUBUSのlistenコマンドと同等な処理を実現するためのC言語実装の方法について紹介しました。

他プロセスと協調して動作する必要があるサービス開発をするときなどで、イベントの受信待ちが必要なケースがあります。

適宜、実装を検討すると良いと思います。

参考文献