コンテンツにスキップ

5.1.3 C言語カスタムコンポーネント作成

本項では、C言語によるカスタムコンポーネントの作成方法について説明します。

5.1.3.1 前提知識#

本項の内容は以下に記す知識・経験を前提としています。

  • C言語を用いた一般的なプログラムの開発経験
  • C言語の開発に必要なツール類の準備
  • Makefileによるビルドの実行方法

また、本項ではSpeeDBee Synapseが稼働する環境内でCのプログラムをビルドする前提で記載しています。 SpeeDBee Synapseが稼働する環境とは別の環境でプログラムをビルドして移動する場合は、その環境に応じてクロスビルドが必要になります。クロスビルドの方法は稼働する環境のマニュアル等をご確認ください。

5.1.3.2 サンプルカスタムコンポーネント#

C言語によるカスタムコンポーネントの実装方法説明のため、シンプルな仕様のカスタムコンポーネントをサンプルとして提供しています。

サンプルカスタムコンポーネントのダウンロード

もしくは、SpeeDBee Synapseをインストール済みの場合、カスタムコンポーネントサンプルも下記の場所にインストールされています。

$ ls /usr/local/speedbeesynapse/share/sample_component_c/*
/usr/local/speedbeesynapse/share/sample_component_c/cmake_project:
CMakeLists.txt  README.md  countandrandom.c  readandlog.c

/usr/local/speedbeesynapse/share/sample_component_c/makefile_project_for_linux:
Makefile  README.md  countandrandom.c  readandlog.c

/usr/local/speedbeesynapse/share/sample_component_c/makefile_project_for_windows:
Makefile  README.md  countandrandom.c  readandlog.c

サンプルカスタムコンポーネントのディレクトリは、ビルド環境に合わせて下記の3種類用意されています。

  • cmake_project

    CMakeを使ってビルドするためのプロジェクトディレクトリです。 Linux/Windowsどちらも同じ手順でビルドできます。

  • makefile_project_for_linux

    Makeを使ってビルドするためのLinux環境専用プロジェクトディレクトリです。

  • makefile_project_for_windows

    WindowsのNMAKEを使ってビルドするためのプロジェクトディレクトリです。

5.1.3.2.1 サンプルコンポーネントのビルド#

各プロジェクトディレクトリでのビルド方法は、それぞれのREADME.mdファイルを参照してください。 どのプロジェクトディレクトリも、ビルドして生成されるコンポーネントは同一のものになります。

SpeeDBee Synapseを実行する環境がWindowsの場合、cmake_projectもしくはmakefile_project_for_windowsのどちらかを選択してください。
実行する環境がLinuxの場合、cmake_projectもしくはmakefile_project_for_linuxのどちらかを選択してください。

5.1.3.2.2 サンプルの内容#

カスタムコンポーネントのサンプルとして本項では下記のサンプルを説明します。

  • count_and_random(countandrandom.c)

    出力ポートに対して以下の2つのカラムを作成し、データを出力し続けるコンポーネントです。

    カラム名 内容
    count 3秒に1回、1から順にカウントアップした値を登録していきます
    random 3秒に1回、[0.0, 100.0)の範囲内の値をランダムに登録します
  • read_and_log(readandlog.c)

    入力ポートからデータを受け取り、それを文字列型カラムとログに出力します。

5.1.3.2.3 実行#

サンプルコンポーネントのビルドに成功すると、Linuxなら.soファイルが、Windowsなら.dllファイルがコンポーネントごとに生成されます。 生成された.so or .dllファイルをWEBUIから登録することで、SpeeDBee Synapseからコンポーネントとして利用できるようになります。 登録手順は カスタムコンポーネントを使用するを参照してください。

ファイルを登録してコアが再起動されると、SpeeDBee Synapseの画面から、他のコンポーネントと同様に新しいカスタムコンポーネントを使用できるようになっています。

カスタムコンポーネント選択

count_and_random、および、read_and_logを繋いで実行することで、それぞれの出力したカラムを表示できます。 下記はcount_and_randomの出力ポートをモニタリングしています。

カスタムコンポーネント同士の接続

5.1.3.3 カスタムコンポーネント実装詳細#

以降では、サンプルのcount_and_random、および、read_and_logのソースコードを例に、カスタムコンポーネントの実装内容を説明していきます。

5.1.3.3.1 インクルードヘッダ#

Cによるカスタムコンポーネントの開発では、ヘッダファイルhiveframework.hをインクルードする必要があります。

#include <hiveframework.h>

 :  以降カスタムコンポーネントの実装

5.1.3.3.2 カスタムコンポーネント情報定義#

Cによるカスタムコンポーネントのソースコードでは、下記のグローバル変数定義により、そのコンポーネントの名前や識別情報、ライフサイクルコールバック関数の定義が必要になります。

  :  カスタムコンポーネントの各種関数の定義

const static HIVE_COMPONENT_DEFINITION info = {
  .uuid            = "2e80ad02-1730-4bdc-b321-2fbc2d1b942e",
  .name            = "count and random",
  .parameter_type  = HIVE_COMPONENT_PARAMETER_NONE,
  .in_ports        = 0,
  .out_ports       = 1,
  .functions     = {
    .constructor        = NULL,      // optional
    .premain            = premain,   // optional
    .main               = mainloop,  // required
    .postmain           = postmain,  // optional
    .destructor         = NULL,      // optional
    .stop               = NULL,      // optional
  },
};
EXPORT_COMPONENT_DEFINITION(info);
メンバ名 定義情報 説明
uuid UUID コンポーネントを識別するためのUUIDです。ランダムなIDを生成してここに設定してください。
name コンポーネント名 コンポーネントの名前を設定します。画面にはこの名前が表示されますのでなるべく他のコンポーネントと被らない方が良いです。
tag タグ コンポーネントへタグを付与します。
"collector","emitter","serializer","action","logic"のいずれかの指定によりコンポーネントベースの配置先を指定できます。省略した場合は、Customへ配置されます。
parameter_type パラメータ種別 このコンポーネントに渡すパラメータの形式をNONE, TEXT, JSONから選択できますが、現状はこの項目は使われていません。
out_ports 出力ポート数 このコンポーネントが持つ出力ポートの数を設定します。他のコンポーネントの入力ポートにデータを渡す場合はこれを1としてください。
in_ports 入力ポート数 このコンポーネントが持つ入力ポートの数を設定します。他のコンポーネントの出力ポートからデータを受け取る場合は、これを1としてください。
functions コールバック関数リスト このコンポーネントの動作を実装した関数のリストを設定します。次節参照。

UUIDについては必ずランダムなIDを生成してください。 他のコンポーネントと同じUUIDにしてしまうと、どちらのコンポーネントも使えなくなることがあります。

5.1.3.3.3 ライフサイクルコールバック定義#

前節のfunctionsメンバは、コンポーネントインスタンスのライフサイクルに基づいて コールされる関数ポインタを設定することができます。

コンポーネントライフサイクル

コールバック関数 説明
constructor コンポーネントのインスタンスが生成されたときにコールされます
destructor コンポーネントのインスタンスが破棄されるときにコールされます
premain コンポーネントを開始するときにmainの前にコールされます
main コンポーネントのメインの処理を実装する関数です
コンポーネントの終了要求を受けるまで、この関数は終了せずにループ処理し続ける必要があります
postmain コンポーネントの終了時、mainの終了直後にコールされます
stop mainの実行中、コンポーネントの停止要求を受けたときに実行されます

前節のfunctions定義は、サンプルコンポーネントのcount_and_randomの末尾に定義されています。

このコンポーネントではconstructor, destructorは不要なため、省略(NULL指定)しています。premain, postmainも不要であれば省略することができます。(このサンプルでも、実際にはログ出力しか実装されていません)

mainは、コンポーネントの処理の本体になりますので、必須です。 実際の関数定義名はmainloopなど、別の名前にしてください。mainだとCの実行形式本体のmain関数と衝突してしまいます。

以降ではサンプルプログラムのmain関数の実装内容を説明します。

5.1.3.3.4 出力ポートのカラム作成/データ登録(count_and_random#

下記はカスタムコンポーネントcount_and_randomのメインの処理です。 これをもとにデータを出力する流れを説明します。

static bool mainloop(HIVE_COMPONENT comp, const char *param, HIVE_STATUS status) {
  HIVE_LOG_INFO("create columns");
  HIVE_OUTCOLUMN clm_count = hive_outport_create_column(comp, OUTPORT1, "count", HIVE_DATA_SCALAR(HIVE_TYPE_INT32), HIVE_COLUMN_OPTION_NONE);
  HIVE_OUTCOLUMN clm_random = hive_outport_create_column(comp, OUTPORT1, "random", HIVE_DATA_SCALAR(HIVE_TYPE_DOUBLE), HIVE_COLUMN_OPTION_NONE);

  int count = 1;
  while (hive_component_runnable(comp)) {
    HIVE_LOG_INFO("insert count");
    if (!hive_outcolumn_insert(clm_count, &count)) {
      HIVE_API_ERROR err = hive_get_api_error();
      HIVE_LOG_ERROR("insert 'count' failed: errcode=0x%08x", err.code);
    }

    HIVE_LOG_INFO("insert random");
    double r = 100 * (double)rand() / (double)RAND_MAX;
    if (!hive_outcolumn_insert(clm_random, &r)) {
      HIVE_API_ERROR err = hive_get_api_error();
      HIVE_LOG_ERROR("insert 'count' failed: errcode=0x%08x", err.code);
    }

    usleep(3000000);
    count++;
  }

  return true;
}
  1. カラムの作成

    初めに、カラムを作成しています。

    HIVE_OUTCOLUMN clm_count = hive_outport_create_column(comp, OUTPORT1, "count", HIVE_DATA_SCALAR(HIVE_TYPE_INT32), HIVE_COLUMN_OPTION_NONE);
    HIVE_OUTCOLUMN clm_random = hive_outport_create_column(comp, OUTPORT1, "random", HIVE_DATA_SCALAR(HIVE_TYPE_DOUBLE), HIVE_COLUMN_OPTION_NONE);
    
    このコンポーネントでは32bit整数のカラムcountと倍精度浮動小数点のカラムrandomを利用しますので、 hive_outport_create_column()関数を使用して引数にカラム名、データ型等を指定して、カラムを生成しています。 戻り値は後にカラムにデータを登録する際に使用しますので、変数に保持しておいてください。

    コンポーネントが出力するすべてのデータは、このように生成したカラムに対して登録していく必要があります。

  2. 定期的な処理の繰り返し

    定期的にデータを登録するため、whileループで繰り返し処理を実装しています

    int count = 1;
    while (hive_component_runnable(comp)) {
      :
    
      usleep(3000000);
      count++;
    }
    
    ループの判定条件で呼び出しているhive_component_runnable()は、コンポーネントが実行状態の間はtrueを返します。画面からコンポーネントの停止を要求すると、この関数がfalseを返すようになりますので、その場合には速やかにこのmainloop関数を終わらせる必要があります。

    繰り返し処理にはこのようなwhileループの他に、hive_component_interval_call()を使用することもできます。 使用方法はリンク先を参照してください。

  3. カラムへのデータ登録

    繰り返し処理の中で、カラムに対してデータを登録します。

    if (!hive_outcolumn_insert(clm_count, &count)) {
      HIVE_API_ERROR err = hive_get_api_error();
      HIVE_LOG_ERROR("insert 'count' failed: errcode=0x%08x", err.code);
    }
    
    double r = 100 * (double)rand() / (double)RAND_MAX;
    if (!hive_outcolumn_insert(clm_random, &r)) {
      HIVE_API_ERROR err = hive_get_api_error();
      HIVE_LOG_ERROR("insert 'count' failed: errcode=0x%08x", err.code);
    }
    
    データの登録はhive_outcolumn_insert()で行えます。 第一引数には事前に作成したカラム変数を、第二引数には登録するデータのポインタを指定します。 このときのポインタは、事前にカラムを作成した時に指定したデータ型と合わせる必要があります。異なる型のデータを指定すると、ソフトウェアが異常終了する原因となりえますので注意してください。

5.1.3.3.5 入力ポートからのデータ読み込み(read_and_log#

下記はカスタムコンポーネントread_and_logのメインの処理です。(コメントやログ出力は省略)

static bool mainloop(HIVE_COMPONENT comp, const char *param, HIVE_STATUS status) {
  HIVE_OUTCOLUMN clm_log = hive_outport_create_column(comp, OUTPORT1, "log", HIVE_DATA_SCALAR(HIVE_TYPE_STRING), HIVE_COLUMN_OPTION_NONE);

  HIVE_CONTINUOUS_READER *creader
    = hive_inport_continuous_reader(comp, INPORT1, hive_timestamp()+5000000000UL);

  hive_time_t last_updated = 0;
  while (hive_component_runnable(comp)) {
    const HIVE_COLUMN_READ_RESULT *read_result = hive_continuous_reader_read(creader);
    if (!read_result) {
      continue;
    }

    if (last_updated < read_result->incolumn_list->updated_at) {
      last_updated = read_result->incolumn_list->updated_at;
    }

    HIVE_RECORD_ITERATOR iter = HIVE_GET_RECORD_ITERATOR(read_result);
    const HIVE_RECORD *record;
    while ((record = HIVE_RECORD_ITERATOR_GET_NEXT(&iter)) != NULL) {
      for (int i=0; i < record->data_count; i++) {
        const HIVE_RECORD_DATA *rd = HIVE_RECORD_GET_DATA(record, i);
        if (rd && rd->data_size != 0) {
          log_record(clm_log, record->timestamp, &read_result->incolumn_list->incolumns[i], rd);
        }
      }
    }
  }
  hive_continuous_reader_release(creader);

  return true;
}

  1. カラムの作成

    このコンポーネントでも、初めにカラムを作成しています。

    HIVE_OUTCOLUMN clm_log = hive_outport_create_column(comp, OUTPORT1, "log", HIVE_DATA_SCALAR(HIVE_TYPE_STRING), HIVE_COLUMN_OPTION_NONE);
    
    このカラムは文字列を出力するためのものです。

  2. データ取得ループ

    入力ポートからのデータを繰り返し受け取る処理が下記になります。

    HIVE_CONTINUOUS_READER *creader
      = hive_inport_continuous_reader(comp, INPORT1, hive_timestamp()+5000000000UL);
    
    :
    while (hive_component_runnable(comp)) {
      const HIVE_COLUMN_READ_RESULT *read_result = hive_continuous_reader_read(creader);
      if (!read_result) {
        continue;
      }
    
      :
    }
    hive_continuous_reader_release(creader);
    

    ここでは4つの関数を利用しています

    • hive_inport_continuous_reader()
      継続的に入力ポートからのデータを取得するためのハンドルを作成します
    • hive_continuous_reader_release()
      データの取得を停止する場合、この関数でハンドルを解放します
    • hive_continuous_reader_read()
      作成したハンドルからデータを取得します。取得したデータはHIVE_COLUMN_READ_RESULT構造体のポインタとして返されますが、データがまだない場合にはNULLが返されることもありますので、その場合は無視してください。
    • hive_component_runnable()
      こちらは前節でも使用しました。 コンポーネントが実行状態の間はtrueを返します。画面からコンポーネントの停止を要求すると、この関数がfalseを返すようになりますので、その場合には速やかにこのmainloop関数を終わらせる必要があります。
  3. 取得したデータのレコードを参照

    hive_continuous_reader_read() 関数により、入力ポートに入ってきたデータをある時間幅分まとめて取得し、HIVE_COLUMN_READ_RESULT構造体のポインタ、read_resultに格納します。 HIVE_COLUMN_READ_RESULT構造体は、下図のように同一時刻のデータを収めた複数のレコードからなり、1つのレコードに複数のカラムのデータが収められています。

    下記のような二重ループにより、この構造体から1レコード、1データずつ処理することができます。 HIVE_GET_RECORD_ITERATOR()HIVE_RECORD_ITERATOR_GET_NEXTがレコードのループ、その内部にある変数iによるfor文がレコード内のデータ処理の繰り返しです

    HIVE_RECORD_ITERATOR iter = HIVE_GET_RECORD_ITERATOR(read_result);
    const HIVE_RECORD *record;
    while ((record = HIVE_RECORD_ITERATOR_GET_NEXT(&iter)) != NULL) {
      for (int i=0; i < record->data_count; i++) {
        const HIVE_RECORD_DATA *rd = HIVE_RECORD_GET_DATA(record, i);
        if (rd && rd->data_size != 0) {
          // ここでrdが1カラム分のデータを保持している
          log_record(clm_log, record->timestamp, &read_result->incolumn_list->incolumns[i], rd);
        }
      }
    }
    
  4. レコード内の1データの扱い

    1つのデータの情報は、HIVE_RECORD構造体HIVE_COLUMN_READ_RESULT構造体incolumn_listメンバから取得することができます。

    項目 本サンプルプログラムでの参照例 内容
    データ名 const char* read_result->incolumn_list->incolumns[i]->data_name データを登録しているカラム名
    コンポーネント名 const char* read_result->incolumn_list->incolumns[i]->source_name データを生成したコンポーネント名
    データ型 HIVE_DATA_TYPE read_result->incolumn_list->incolumns[i]->data_type データを登録しているカラムのデータ型
    データサイズ uint32_t HIVE_RECORD_GET_DATA(record, i)->data_size 実データのデータサイズ
    データアドレス char * HIVE_RECORD_GET_DATA(record, i)->data 実データを格納したアドレス

    上記の通り、実データはconst charポインタとなっています。様々なデータ型を受ける可能性があるため、 HIVE_DATA_TYPEで識別してそれぞれの型に応じてキャストして参照する必要が有ることにご注意ください。

5.1.3.3.6 ログ出力#

コンポーネントでは任意のタイミングでログを出力することができます。

  HIVE_LOG_INFO("create columns");
  HIVE_LOG_INFO("x=%d, y=%f, z=%s", x, 12.34, "aiueo");
  HIVE_LOG_ERROR("insert 'count' failed: errcode=0x%08x", err.code);

上記のように、標準ライブラリのprintf()と同じ形式でログを出力できます。 出力したログは、画面からダウンロードすることができます。 「各種ログ」を参照してください。

5.1.3.3.7 コンポーネントパラメータ#

画面上からコンポーネントインスタンスを生成すると、その設定項目としてコンポーネントの実行時パラメータを設定することができます。

カラムコンポーネント設定画面

パラメータの種別としてはJSONでもSTRINGでも利用可能です。 どちらを指定した場合でも、Cで実装されたコンポーネントにおいては、main関数の第二引数にconst char*型の文字列として受け渡されますので、これを標準ライブラリのsscanfで解析したり、直接文字列として扱ったりすることが可能です。

static bool mainloop(HIVE_COMPONENT comp, const char *param, HIVE_STATUS status) {
  int val1, val2;
  sscanf(param, "%d,%d", &val1, &val2); // perform parameter as a number string

  // some process using 'val1' and 'val2'.
}

JSON形式のパラメータを扱う場合も、paramに文字列としてJSON形式が格納されますので、これをパースする必要があります。 JSONの扱いは本システムのAPIとしては用意していませんので、汎用的なライブラリの使用を検討してください。

参考

5.1.3.4 カスタムコンポーネント用API#

本項で紹介した関数以外にも、多数のAPI関数が用意されています。 詳細は付録のカスタムコンポーネントC-APIリファレンスを参照してください。

5.1.3.5 カスタムコンポーネントを使用する#

ここでは、作成したカスタムコンポーネントをSpeeDBee Synapseで使用する方法について説明します。

5.1.3.5.1 登録#

次の手順で、ビルドしたカスタムコンポーネントをSpeeDBee Synapseに登録することができます。

  1. 設定メニューアイコンを押下し、「カスタム(C)」を選択します。

  2. 「追加」を押下し、ビルド済みのカスタムコンポーネントのsoファイルを登録します。

  3. 「閉じる」を押下します。

  4. 確認ダイアログで「はい」を選択し、変更を反映するために、システムを再起動させます。

  5. 左メニューに、登録したカスタムコンポーネントが反映されます。

    ファイルの保存先

    登録したカスタムコンポーネントのファイルは、次のディレクトリに保存されます。

    /var/speedbeesynapse/custom_component_so

    保存先のディレクトリは、画面項目「保存先」に表示されます。

5.1.3.5.2 設定画面#

カスタムコンポーネントの設定画面の項目は次の通りです。

項目 説明
名称 コンポーネントの名前を入力
※他のコンポーネント名と重複する事はできません。
自動起動無効 コンポーネントの自動起動を無効にする場合ON
パラメータータイプ パラメータの種類を次の中から選択
・STRING:文字列
・JSON:JSON文字列
パラメーター コンポーネントパラメータを入力
スクリプトで使用するファイルをアップロードする(証明書など) カスタムコンポーネントの処理で使用する、証明書などのファイルをアップロードする場合ON
ファイル追加 押下すると、スクリプトで使用するファイルをアップロードすることができます。
※新しく追加されたファイルは、コンポーネントの設定を保存した時にアップロードされます。
ファイルパス アップロードしたファイルの保存先
※パラメータでファイルパスを使用する場合、変数を用いることができます。詳細は、表題の「ファイルパス」の右のアイコンをクリックすると確認することができます。

設定画面のパラメータ入力部分に、ユーザーが定義した画面項目を表示するように変更することができます。 詳細は「カスタムUI作成」をご覧ください。

5.1.3.5.3 削除#

次の手順で、登録したカスタムコンポーネントをSpeeDBee Synapseから削除することができます。

  1. 設定メニューアイコンを押下し、「カスタム(C)」を選択します。

  2. カスタムコンポーネントの「削除」を押下します。

  3. 確認ダイアログで「はい」を選択します。

  4. カスタムコンポーネントが削除されます。

5.1.3.5.4 ダウンロード#

次の手順で、登録したカスタムコンポーネントをダウンロードすることができます。

  1. 設定メニューアイコンを押下し、「カスタム(C)」を選択します。

  2. カスタムコンポーネントの「取得」を押下します。

  3. カスタムコンポーネントのファイルがダウンロードされます。