コマンドの受信処理

モバイルアプリから Thing Interaction Framework に送信されたコマンドは、ターゲットとなっている Thing に配信されます。Thing にコマンドが届くと、初期化の際に指定したアクションハンドラーが Thing-IF SDK から呼び出されます。

アクションハンドラー

アクションハンドラーは以下のような形式をした関数です。コマンドに複数のアクションが含まれていた場合は、その数だけ呼び出されます。

static kii_bool_t action_handler(
        const char* schema,
        int schema_version,
        const char* action_name,
        const char* action_params,
        char error[EMESSAGE_SIZE + 1])
{
  return KII_TRUE;
}

schemashema_version には、モバイルアプリがコマンドの送信時に指定したスキーマ名とスキーマバージョンが渡されます。扱い方は次の スキーマのチェック のセクションで示します。

action_nameaction_params には、受け取ったアクションの詳細が渡されます。

error はこの関数からの出力パラメーターです。アクションの実行に成功したときは KII_TRUE を返し、error はそのまま戻します。失敗したときは KII_FALSE を返し、error にエラーメッセージを格納します。サンプルの実行Bulb is overheating が出力されていたのは、アクションハンドラーが error にこの文字列を格納して KII_FALSE を返していたためです。

たとえば、コマンドとステートの設計 で設計した turnPower アクションを受け取った場合、各パラメーターは下の表のような値を取ります。

引数 説明 値の例
schema スキーマ名 HelloThingIF-Schema
schema_version スキーマバージョン 1
action_name アクション名 turnPower
action_params アクションのパラメーター(JSON 形式) {"power":true}

同一コマンド内で setBrightness アクションも受け取った場合は、turnPower の処理の完了直後に action_namesetBrightnessaction_params{"brightness":100} などの状態でアクションハンドラーがもう一度呼び出されます。

スキーマのチェック

アクションハンドラーでアクションを受信した際、まずはスキーマ名とスキーマバージョンをチェックして、このプログラムで処理できるアクションであることを確認します。対応できないものはエラー応答できます。

たとえば、以下の状況を想定します。

初めはすべてのコンポーネントが Ver.1 で構成されています。この状態ではやりとりされるすべてのコマンドは、各コンポーネントの想定どおりで扱われます。

その後、モバイルアプリと Thing のファームウェアがそれぞれバージョンアップしたとします。ただし、それらはすべてのデバイスへすぐにインストールされるとは限りません。下の図の 2 のように、古いファームウェアを使っている Thing では、認識できないコマンドを受け取る可能性があります。

このような場合、Thing はスキーマとスキーマバージョンをチェックして、不明なものに対してエラーを返すことができます。モバイルアプリでは、特定のエラーメッセージを受けたとき、Thing のファームウェアをバージョンアップする指示を表示するなどの対策が取れます。

Hello Thing-IF で実装されているチェックでは、特定のスキーマ名とスキーマバージョンであることを確認しています。バージョン 2 以降では、複数のバージョンをサポートするように実装することもできます。

static kii_bool_t action_handler(
        const char* schema,
        int schema_version,
        const char* action_name,
        const char* action_params,
        char error[EMESSAGE_SIZE + 1])
{
  ......

  printf("Schema=%s, schema version=%d, action name=%s, action params=%s\n",
         schema, schema_version, action_name, action_params);

  if (strcmp(schema, SCHEMA_NAME) != 0 || SCHEMA_VERSION != 1) {
      prv_action_error(error, "Invalid schema.");
    return KII_FALSE;
  }

  ......

  return KII_TRUE;
}

prv_action_error は以下のようなヘルパーメソッドです。error と画面にメッセージを出力します。

static void prv_action_error(char error[EMESSAGE_SIZE + 1], const char *message)
{
  memset(error, 0x00, EMESSAGE_SIZE + 1);
  strncpy(error, message, EMESSAGE_SIZE);
  printf("Error: %s\n", message);
}

error には EMESSAGE_SIZE + 1 バイトの領域を用意しています。アクションに含められている文字列をそのままエラーメッセージとして転送すると、バッファーオーバーフローによるセキュリティホールの原因になります。このサンプルコードのように、strncpy() などで長さのチェックを行うようにしてください。

アクションの解析

処理できるアクションであることを判断できたら、アクション名とパラメーターの文字列を解析して、アクションに応じた処理を行います。

Hello Thing-IF でアクションを解析する処理は、次のように実装されています。

static kii_bool_t action_handler(
        const char* schema,
        int schema_version,
        const char* action_name,
        const char* action_params,
        char error[EMESSAGE_SIZE + 1])
{
  prv_smartlight_t smartlight;

  ......

  memset(&smartlight, 0x00, sizeof(smartlight));
  if (strcmp(action_name, "turnPower") == 0) {
    kii_json_field_t fields[2];

    memset(fields, 0x00, sizeof(fields));
    fields[0].path = "/power";
    fields[0].type = KII_JSON_FIELD_TYPE_BOOLEAN;
    fields[1].path = NULL;
    if (prv_json_read_object(action_params, strlen(action_params),
                    fields, error) !=  KII_JSON_PARSE_SUCCESS) {
      prv_action_error(error, "Invalid turnPower in JSON.");
      return KII_FALSE;
    }
    if (prv_get_smartlight_info(&smartlight) == KII_FALSE) {
      prv_action_error(error, "Failed to get smartlight info.");
      return KII_FALSE;
    }
    smartlight.power = fields[0].field_copy.boolean_value;
    if (prv_set_smartlight_info(&smartlight) == KII_FALSE) {
      prv_action_error(error, "Failed to set smartlight info.");
      return KII_FALSE;
    }
  } else if (strcmp(action_name, "setBrightness") == 0) {
    kii_json_field_t fields[2];

    memset(fields, 0x00, sizeof(fields));
    fields[0].path = "/brightness";
    fields[0].type = KII_JSON_FIELD_TYPE_INTEGER;
    fields[1].path = NULL;
    if(prv_json_read_object(action_params, strlen(action_params),
                    fields, error) !=  KII_JSON_PARSE_SUCCESS) {
      prv_action_error(error, "Invalid brightness in JSON.");
      return KII_FALSE;
    }
    if (prv_get_smartlight_info(&smartlight) == KII_FALSE) {
      prv_action_error(error, "Failed to get smartlight info.");
      return KII_FALSE;
    }
    if (smartlight.brightness == 100 && fields[0].field_copy.int_value == 100) {
      prv_action_error(error, "Bulb is overheating");
      return KII_FALSE;
    }
    smartlight.brightness = fields[0].field_copy.int_value;
    if (prv_set_smartlight_info(&smartlight) == KII_FALSE) {
      prv_action_error(error, "Failed to set smartlight info.");
      return KII_FALSE;
    }
  } else {
    prv_action_error(error, "Invalid action.");
    return KII_FALSE;
  }

  if (smartlight.power) {
    printf("Light: Power on, Brightness: %d\n", smartlight.brightness);
  } else {
    printf("Light: Power off\n");
  }

  return KII_TRUE;
}

アクションハンドラーのロジックは、基本的に action_name に対して、ifelse ifelse if ~…~ else のようになるはずです。各アクションに対して、それぞれパラメーター action_params の解析処理を行って、アクションの実行処理を行います。

Hello Thing-IF の場合は、コマンドとステートの設計 で設計したとおり、解析処理を行います。1 つ目の ifaction_nameturnPower の場合を、2 つ目の ifsetBrightness の場合を解析します。

いずれの解析処理も、以下の手順を取っています。

  1. JSON の解析

    action_name に対して、action_params で渡されたアクションのパラメーターを解析します。解析する文字列はスキーマで設計したとおりです。

    JSON の解析処理 は、kii_json ライブラリーで提供されています。Hello Thing-IF では、prv_json_read_object() に用意したヘルパー関数から呼び出せます。

    turnPower の場合、JSON のパス /power にある boolean 値を読み取ります。setBrightness の場合、JSON のパス /brightness にある integer 値を読み取ります。kii_json_field_t 構造体の配列にこれらをセットして、prv_json_read_object を呼び出します。

    kii_json ライブラリーでは、1 つの JSON 文字列から、複数のフィールドの値を同時に取得できます。そのため、取得対象のフィールドを指定する kii_json_field_t 構造体は配列として連続領域に宣言し、path メンバーの NULL で定義の終端を表現します。

    prv_json_read_object()KII_JSON_PARSE_SUCCESS を返した場合は、解析に成功したことを表します。

  2. prv_smartlight_t 構造体の取得

    ステートの送信のため、最新の LED ライトの状態をグローバル変数に保存しています。ここでは、その値を prv_get_smartlight_info() 関数で取得しています。

  3. 値の更新

    読み出した prv_smartlight_t 構造体の値を更新します。

    ここでは、JSON の解析処理の結果を読み取ります。解析開始時に type メンバーでフィールドの型を指示しておくと、解析結果が field_copy メンバーの共用体にコピーされます。field_copy.boolean_value では boolean 値を、field_copy.int_value では integer 値を取得できます。

    なお、明るさの更新の際には、テスト用のエラー処理も行っています。直前の値が 100 で、今回も 100 に更新しようとしている場合、Bulb is overheating エラーとする処理を組み込んでいます。実際のプログラムでも同様の方法でエラーを返すことができます。

  4. prv_smartlight_t 構造体の格納

    最後に、prv_set_smartlight_info() 関数を呼び出して、アクションによって更新された LED ライトの状態をグローバル変数に格納します。

最後に、読み出した値を printf() で出力して、KII_TRUE を返します。KII_TRUE を返した場合、Thing-IF SDK は、そのアクションが成功したものとして Thing Interaction Framework に応答します。モバイルアプリからはアクションリザルトとして結果を取得できます。

Hello Thing-IF ではアクションの値を画面に表示するだけの動きですが、実際のプログラムでは、JSON から読み出した値をアクチュエーターを動かすタスクに中継するなどして、ハードウェアを制御することになるはずです。

ハンドラー間の値の共有

アクションハンドラーとステートハンドラーは非同期に動くため、更新中のデータを読み出さないようにする必要があります。prv_get_smartlight_info()prv_set_smartlight_info() の実装では、Pthreads のミューテックスを使って同時更新からグローバル変数を保護しています。

prv_smartlight_t 構造体と、その読み書きの関数は以下のような実装です。

typedef struct prv_smartlight_t {
    kii_json_boolean_t power;
    int brightness;
} prv_smartlight_t;

static prv_smartlight_t m_smartlight;
static pthread_mutex_t m_mutex;

static kii_bool_t prv_get_smartlight_info(prv_smartlight_t* smartlight)
{
  if (pthread_mutex_lock(&m_mutex) != 0) {
    return KII_FALSE;
  }
  smartlight->power = m_smartlight.power;
  smartlight->brightness = m_smartlight.brightness;
  if (pthread_mutex_unlock(&m_mutex) != 0) {
    return KII_FALSE;
  }
  return KII_TRUE;
}

static kii_bool_t prv_set_smartlight_info(const prv_smartlight_t* smartlight)
{
  if (pthread_mutex_lock(&m_mutex) != 0) {
    return KII_FALSE;
  }
  m_smartlight.power = smartlight->power;
  m_smartlight.brightness = smartlight->brightness;
  if (pthread_mutex_unlock(&m_mutex) != 0) {
    return KII_FALSE;
  }
  return KII_TRUE;
}

いずれの関数でも、ミューテックスでロックした後、グローバル変数 m_smartlight にアクセスし、引数の値とのやりとりを行っています。最後にロックを解除しています。

実際のプログラムでも、アクションハンドラーとステートハンドラーで値を共有する場合は、これらが同時に動く前提で実装する必要があります。このサンプルと同様に、ミューテックスなどを使って同期化するのが実装方法の 1 つです。


次は...

ステートの登録処理を確認します。

ステートの登録処理 に移動してください。

より詳しく学びたい方へ

  • Thing-IF SDK でのコマンドの受信方法の詳細は コマンドの実行 を参照してください。
  • 実際のプログラムではより複雑な JSON を解析する必要があります。Thing-IF SDK が提供している JSON の解析処理の詳細は JSON の解析 を参照してください。