Thing Interaction Frameworkを使ってみる 前編

2015/12/22

カテゴリー:開発お役立ち

Kii cloudに、新たにThing Interaction Framework(以下Thing-IF)という新機能が追加になりました(関連記事)。
今回は、Thing-IFとそのSDKであるThing-IF SDKの概要、およびサンプルアプリケーションによる動作確認方法についてご紹介したいと思います。

Thing SDKとThing-IF、Thing-IF SDK

Kii cloudには、既にThing特有のデータ管理やオーナー管理、プッシュ通知等に対応しており、Kii cloud SDKのThing版であるThing SDKがリリースされています。
今回追加となったThing-IFは、これらを置き換えるものではなく、Thingを利用したアプリケーションをより簡単にかつ迅速に構築できるようするためのフレームワークです。Thing-IF SDKは、そのフレームワークを利用するためのSDKということになります。
Kii cloudのThing対応機能やThing SDKに変更はありませんので、これまで通りThing SDKを利用した開発は可能ですし、Thing-IFとThing-IF SDKでできることはThing SDKでも開発可能です。
では、どのような場合にThing-IFを利用するのでしょうか?

Thing-IFの利用ケース

Thingには、体重計や血圧計、温度センサーなどの各種測定を行うものや、電球やエアコン、TV、冷蔵庫などの家電製品など、さまざまなものがあり、それらを利用するアプリケーションも千差万別です。
Thing-IFは、これらのなかから、典型的なThingの利用シーンを想定し、機能モデルとしてフレームワーク化して、APIを提供するものです。Thing-IF SDKにより、提供されたAPIを利用して、それらの利用シーンを簡単かつ迅速に実現できます。
Thingの機能やアプリケーションの目的からは、Thing-IFで想定している利用シーンに一致しない場合もあります。その場合はThing SDKを利用することになります。

Thing-IFの機能モデル

Thing-IFが想定する利用シーンは、例えば、電球やエアコンをThingとして利用する場合です。
エアコンを例としてThing-IFの機能モデルを見ていきましょう。

ステートの登録と参照

Thing-IFでは、Thingの状態を表す値をステートと呼んでいます。
エアコンの例では、電源の状態や設定温度、風量、また現在の室温や湿度などです。
これらは、ThingIF-SDKにより、Thingから一定間隔で自動的にKii cloudにアップロードされ保存されます(今回のバージョンでは1世代のみの保存となっており、次期以降のバージョンで履歴の保存に対応する予定です)。
登録されたステートはモバイルアプリから参照することができます。
tif_1222_1.jpg
※ステートはコマンドの実行完了時にもアップロードされます。

コマンドの実行

エアコンの電源のON/OFFや、設定温度の変更など、モバイルアプリからThingに対してコマンドを送ることができます。コマンドはMQTTによりThingに通知されます。
エアコンの電源操作や設定温度の変更の操作は、各々アクションと呼ばれ、コマンドには複数のアクションを登録することができます。
エアコンの電源をONにする際は、設定温度や風量も一緒に設定したい場合があります。その場合は、電源をONにするアクションとともに、設定温度を設定するアクションおよび風量を設定するアクションをコマンドに登録して送信します。
コマンドを受け取ったThingでは、コマンドの内容を解析し、各アクションの動作を実行します。
Thingはアクションの動作が行われたことをThing-IFに通知し、Thing-IFはプッシュ通知によりモバイルアプリに通知します。ここで通知されるのはコマンドIDのみです。
モバイルアプリは、必要に応じて、コマンドIDを利用して動作結果を取得します。
tif_1222_2.jpg

トリガーによるコマンドの実行

Thing-IFでは、ステートを監視し、ある条件に合致した場合に、予め登録しておいたコマンドを実行することができます。
例えば、室温のステートを監視し、室温のステートが30℃以上になったらエアコンの電源をONにする、ということが可能です。
この「室温のステートが30℃以上になったら」という条件と「エアコンの電源をONにする」というコマンドのセットをトリガーと呼んでいます。
トリガーは、モバイルアプリケーションから登録、削除、有効/無効の設定を行います。
tif_1222_3.jpg

ユーザー(モバイルアプリ)とThingの紐づけ

家庭にあるエアコンをモバイルアプリから操作できる場合、部外者が勝手に操作できてしまうのは問題です。通常、家庭内のエアコンを操作できるのは所有者やその家族のみです。
Thing-IFでは、Kii cloudに登録されている特定のユーザー(またはグループ)をThingのオーナーとして紐づけます。これにより、ステートの参照やコマンドの送信はオーナーのみに限定されます。
Thing-IFではこの紐づけを初期登録(Onboarding)と呼んでいます。
初期登録には、モバイルアプリから紐づける方法と、モバイルアプリとThingの両方から紐づける方法があり、どちらも結果は同じです。どちらの方法を利用するかは、構築するサービスの内容やThingの機能よって選択することになります。
tif_1222_4.jpg

Thing-IFを利用したアプリの開発

Thing-IFの概要はお分かりいただけたでしょうか?
それでは、次に、エアコンのリモートコントロールを行うアプリを例として、Thing-IFを利用したアプリ開発の流れを見ていきたいと思います。

エアコンのシナリオ

ここでは、エアコンのシナリオとして、以下を想定します。

  • エアコンの機能
    • 電源のON/OFF
    • 温度設定
    • 風量設定
    • 室温と湿度の測定
    • wifiによるインターネット接続
  • Thing-IFによるエアコンのリモートコントロール
    • エアコンにはメーカーによって割り振られた固有のIDとパスワードが予め登録されており、最初に電源を入れた際に、IDとパスワードを利用して、Kii cloudに登録される。
    • ユーザーは、スマートフォンにKii cloudを利用したエアコンのリモートコントロール用のモバイルアプリをダウンロードし、ユーザー登録を行う。
    • さらに、ユーザーは、エアコンに添付の二次元バーコードをモバイルアプリで読み取り、エアコンのIDとパスワードを取得し、エアコンとユーザー(モバイルアプリ)の紐づけを行う。
    • 紐づけ後は以下が可能となる。
      • モバイルアプリを利用して、エアコンの状態や、エアコンが測定する室温や湿度を確認する。
      • モバイルアプリから、エアコンの電源のON/OFFや、設定温度の設定、風量の設定を行う。
      • モバイルアプリから、ある室温を超えたら電源をONにするなどのトリガーを登録する。

開発手順

Thing-IFを利用したアプリ開発では、次の4つのことを行います。

  • Kii cloudへのアプリの登録
  • スキーマの定義
  • Thingアプリの実装
  • モバイルアプリの実装

Kii cloudへのアプリの登録

Thing-IFを利用する場合も、アプリ自体は通常のアプリと変わりません。
開発者ポータルでアプリを登録して、APP IDとAPP KEYを取得します。

スキーマの定義

Thing-IFでは、ThingとKii cloud、モバイルアプリの間で送受信を行うデータやコマンドの形式を予め定義しておくことで、標準化されたAPIを利用し、簡潔なコードによりアプリの実装を行うことを可能にしています。
この定義のことをスキーマと呼んでいます。
以下の3種類の定義を行います。

  • スキーマ名:Thingとモバイルアプリ間でのスキーマの識別に利用されます。
  • ステート:送受信するステートの名称と型を定義します。
  • アクション:送受信するアクションの名称と引数の型を定義します。

エアコンの例では、例えば以下のような定義を行うことになります。

種類名前備考
スキーマ名 AirConditioner-Demo - スキーマの識別名
(メソッドの引数として指定) 数値 スキーマのバージョン
ステート power ブール値 エアコンの電源の状態
ON:true OFF:false
presetTemperature 数値 エアコンの設定温度
fanSpeed 数値 エアコンの風量
currentTemperature 数値 現在の室温
currentHumidity 数値 現在の湿度
アクション turnPower ブール値 エアコンの電源のON/OFF
setPresetTemperature 数値 エアコンの設定温度の設定
setFanSpeed 数値 エアコンの風量の設定

Thingアプリの実装

現時点で、Embedded Linux 向けのリファレンス実装が提供されており、ソケットやマルチタスクがサポートされています。
また、Thing-IF SDK のソースコードをダウンロードすると、その中にサンプルプログラムが含まれています。
最初は、このサンプルプログラムをベースに実装していくのがよいでしょう。
実装の流れは以下になります。
コードの詳細はドキュメントやサンプルプログラムを参照してください。

1. 初期化コードの実装

※ここではモバイルアプリとThingの両方から紐づける場合について記載します。
まずThing-IF SDKを初期化します。


kii_bool_t result;

result = init_kii_thing_if(&kii_thing_if, EX_APP_ID, EX_APP_KEY, EX_APP_SITE,
        &command_handler_resource, &state_updater_resource, NULL);
if (result == KII_FALSE) {
  // Failed to initialize the SDK
}

EX_APP_ID、 EX_APP_KEYは各々、アプリのAPP ID、APP KEY、EX_APP_SITEはKii cloudのサイトです(JPの場合は"api-jp.kii.com")。その他、必要なポインタを指定しています。

次にThingから初期登録を行います。


kii_bool_t result;

result = onboard_with_vendor_thing_id(&kii_thing_if,
      VENDOR_THING_ID, THING_PASSWORD, THING_TYPE, THING_PROPERTIES);
if (result == KII_FALSE) {
  // Failed to onboard
}

VENDOR_THING_ID、THING_PASSWORDは、Thingのベンダーによって割り当てらる固有のIDとパスワードです。
THING_TYPE、 THING_PROPERTIESは、将来の拡張やKii cloud SDKとの連携に利用するもののであるため、現状はNULLで問題ありません。
その他、必要なポインタを指定しています。

このあと、モバイルアプリ側で初期登録を行うことで、ステートの登録/参照やコマンドの送受信ができるようになります。

2. ステートハンドラの実装

ステートを返すコールバック関数であるステートハンドラを実装します。
一定間隔でステートをアップロードするためのハンドラと、コマンドの実行完了時にステートをアップロードするためのハンドラがありますが、両者に同じものを利用することも可能です。
コードの例は以下になります。


kii_bool_t state_handler(
        kii_t* kii,
        KII_THING_IF_WRITER writer)
{
  char buf[256];

  if ((*writer)(kii, "{\"power\":") == KII_FALSE) {
    return KII_FALSE;
  }
  if (get_power() != 0) {
    if ((*writer)(kii, "true,") == KII_FALSE) {
      return KII_FALSE;
    }
  } else {
    if ((*writer)(kii, "false,") == KII_FALSE) {
      return KII_FALSE;
    }
  }

  sprintf(buf,
      "\"presetTemperature\":%d,\"fanSpeed\":%d,\"currentTemperature\":%d,\"currentHumidity\":%d}",
      get_presetTemperature(),
      get_fanspeed(),
      get_currentTemperature(),
      get_currentHumidity());
  if ((*writer)(kii, buf) == KII_FALSE) {
    return KII_FALSE;
  }

  return KII_TRUE;
}

この例では、例えば、


{"power":true,"presetTemperature":25,"fanspeed":5,"currentTemperature":28,"currentHumidity":65}

というような文字列が生成されますが、これは、スキーマ定義に則して、モバイルアプリ側のステート取得用クラスに対応しており、各々の値は、ステート取得用クラスの対応するフィールドに設定されます。

3. アクションハンドラの実装

コマンドは、Thing-IFから MQTT のプッシュ通知によって Thing に届きます。MQTT でプッシュ通知を受け取ると、SDK からアクションハンドラが呼び出されます。
MQTT の初期化処理などは SDK の内部で自動的に実行されます。リファレンス実装を使用する場合、特別な作業は不要です。

コードは以下になります。


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;
}

処理が成功した場合は、KII_TRUEを、その他の場合はKII_FALSEを返します。この値は、モバイルアプリに通知されます。
schema、schema_versionは、スキーマの定義で定義した値です。action_nameは、スキーマで定義されたアクション名、action_paramsはコマンドのパラメータでJSON文字列で渡されます。errorはエラーメッセージを格納する領域のポインタで、KII_FALSEを返す場合にこれを利用してメッセージを返却することができます。
[### 指定されたアクションの処理 ###]では、各種パラメータのチェックを行い、指定されたアクションの処理を行います。
schema、schema_versionについては、Thingとモバイルアプリ間で、スキーマやバージョンが一致しているかを確認し、一致しない場合は、必要に応じて適切な応答を行います。
次に、action_nameをチェックし、スキーマに定義したどのアクションかを確認し、処理を分岐します。
例えば、"turnPower"の場合は、電源のON\OFFを行う処理に分岐します。
action_paramsを解析して目的の処理を行います。
例えば、{"turnPower" : true}の場合、電源をONにします。
アクションの実行結果は、SDKにより、アクションリザルトとしてKii cloudにアップロードされ、プッシュ通知によりモバイルアプリに通知されます。

モバイルアプリの実装

モバイルアプリ側は、スキーマクラスの実装と、初期登録、ステートの参照、コマンドの実行の各処理を実装し、必要に応じてトリガーの設定の処理を実装します。
また、コマンドの処理結果であるアクションリザルトを受信するために、Push通知を受信できるようにして、必要に応じてアクションリザルトに対する処理を実装します。
ここではAndroidを例に実装の流れを見ていきたいと思います。
開発環境の構築やPush通知の設定に関しては、サンプルアプリケーションによる動作確認方法の紹介で触れますが、Kii cloudの範囲外のため、詳細は各種ドキュメントを参照してください。

1. スキーマクラスの実装

最初に定義したスキーマに従い、ステートを参照するためのクラス、モバイルアプリからアクションを送信するためのクラス、およびアクションリザルトを受け取るためのクラスを実装します。
エアコンの例では以下のようになります。

ステートを参照するためのクラスは、以下のように、TargetStateのサブクラスとして、各ステートをフィールドとして定義します。


public class AirConditionerState extends TargetState {
  public boolean power;
  public int presetTemperature;
  public int fanSpeed;
  public int currentTemperature;
  public int currentHumidity;
}

アクションを送信するためのクラスは、Actionクラスのサブクラスとして、アクションリザルトを受け取るためのクラスは、ActionResultのサブクラスとして以下のように実装します。
エアコンの例では3つのアクションがあるため、アクションリザルトと合わせて6つのクラスとなります。

TurnPower

public class TurnPower extends Action {
  public boolean power;

  @Override
  public String getActionName() {
    return "turnPower";
  }
}


public class TurnPowerResult extends ActionResult {

  @Override
  public String getActionName() {
    return "turnPower";
  }
}

SetPresetTemperature

public class SetPresetTemperature extends Action {
  public int presetTemperature;

  @Override
  public String getActionName() {
    return "setPresetTemperature";
  }
}


public class SetPresetTemperatureResult extends ActionResult {
  @Override
  public String getActionName() {
    return "setPresetTemperature";
  }
}

SetFanSpeed

public class SetFanSpeed extends Action {
  public int fanSpeed;

  @Override
  public String getActionName() {
    return "setFanSpeed";
  }
}


public class SetFanSpeedResult extends ActionResult {
  @Override
  public String getActionName() {
    return "setFanSpeed";
  }
}

2. 初期化コードの実装

2-1. Kii cloud SDKの初期化

まずKii cloud SDKを初期化します。これは、Thing-IFを利用しない通常のアプリの場合と同じです。


Kii.initialize(getApplicationContext(), "___APPID___", "___APPKEY___", Kii.Site.JP, false);

___APPID___、 ___APPKEY___は各々、アプリのAPP ID、APP KEYを指定します。
Kii clludの他のAPIの実行に先立って1回実行されればよいため、通常はApplicationを継承したクラスのonCreate内で実行します。

2-2. Thing-IF SDKの初期化

次にThing-IF SDKを初期化しますが、引数として、OwnerインスタンスとSchemaインスタンスが必要です。
これらは、以下のように生成します。

Ownerインスタンス
Ownerインスタンスは、Thingに紐づけるKii cloudのユーザーです。
モバイルアプリにログイン中のユーザーや、グループ、あるいは仮ユーザーを指定できます。
例えば、モバイルアプリにログイン中のユーザーの場合は、以下のようにしてownerインスタンスを取得します。


KiiUser user = KiiUser.getCurrentUser();
TypedID typedUserID = new TypedID(TypedID.Types.USER, user.getID());
Owner owner = new Owner(typedUserID, user.getAccessToken());

Schemaインスタンス
Schemaインスタンスは、ステートとコマンドのクラスをまとめたインスタンスです。
エアコンの例では以下のようにして生成します。


SchemaBuilder sb = SchemaBuilder.newSchemaBuilder("airConditioner",
      "AirConditioner-Demo",
      1, AirConditionerState.class);
sb.addActionClass(TurnPower.class, TurnPowerResult.class).
      addActionClass(SetPresetTemperature.class, SetPresetTemperatureResult.class).
      addActionClass(SetFanSpeed.class, SetFanSpeedResult.class);

Schema schema = sb.build();

OwnerインスタンスとSchemaインスタンスにより、Thing-IF SDKを初期化します。


ThingIFAPI api;

ThingIFAPIBuilder ib = ThingIFAPIBuilder.newBuilder(getApplicationContext(),
    "___APPID___", "___APPKEY___", Site.JP, owner);
ib.addSchema(schema);
api = ib.build();

___APPID___、 ___APPKEY___は各々、アプリのAPP ID、APP KEYを指定します。
初期化で生成したThingIFAPI インスタンスを利用して、Push受信のためのユーザー端末の登録と、Thingとモバイルアプリの紐づけを行います。

2-3. ユーザー端末のインストール

プッシュ通知を受信するためにThingIFAPI インスタンスを利用して端末のインストールを行います。
以下はGCMを利用する例です。


try {
  api.installPush(deviceToken, PushBackend.GCM);
} catch (ThingIFException e) {
  // Installation failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

deviceTokenには、GCMの初期化処理で取得したデバイストークンを指定します。
GCMの初期化処理については、サンプルコードやドキュメントを参照してください。

2-4. 初期登録

※ここではモバイルアプリとThingの両方から紐づける場合について記載しています。

Thing-IFの初期化で生成したThingIFAPI インスタンスを利用して、モバイルアプリからの初期登録を行います。


try {
  api.onboard(VENDOR_THING_ID, THING_PASSWORD, null, null);
} catch (ThingIFException e) {
  // Onboarding failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

VENDOR_THING_ID、THING_PASSWORDは、Thingのベンダーによって割り当てらる固有のIDとパスワードです。
紐づけるThing側の初期登録で利用される値と同じ値です。
エアコンのシナリオでは、これらが記録された二次元バーコードを読み取って、値を取得する想定です。
モバイルアプリの初期登録が完了すると、ステートの参照や、コマンドの送信とコマンドリザルトの受信、トリガーの登録ができるようになります。

3. ステートの参照

Thingからアップロードされるステートは、Thing-IFの初期化で生成したThingIFAPI インスタンスにより参照できます。
エアコンの例では、以下のようにして参照します。


try {
  AirConditionerState state = api.getTargetState(AirConditionerState.class);
} catch (ThingIFException e) {
  // Getting failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

4. コマンドの実行

4-1.コマンドの送信

コマンド送信は、実行したいアクションを配列にして、Thing-IFの初期化で生成したThingIFAPI インスタンスにより送信します。
エアコンの例として、電源をON、設定温度を25℃、風量を3に設定するコマンドの送信は以下のようになります。


List<action> actions = new ArrayList<action>();

TurnPower action1 = new TurnPower();
action1.power = true;

SetPresetTemperature action2 = new SetPresetTemperature();
action2.presetTemperature = 25;

SetFanSpeed action3 = new SetFanSpeed();
action3.fanSpeed = 3;

actions.add(action1);
actions.add(action2);
actions.add(action3);

try {
  Command command = api.postNewCommand("AirConditioner-Demo", 1, actions);
} catch (ThingIFException e) {
  // Posting failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

上記の例では3つのアクションを登録していますが、2つ、または1つのみの登録も可能です。

4-2.コマンドリザルトの受信

Thingによりコマンドが実行され、コマンドリザルトがアップロードされると、プッシュ通知によりモバイルアプリに通知されます。
プッシュ通知で通知されるのは、commandID のみです。
以下は、GCMのプッシュ通知を受信するハンドラの例です。


public class KiiPushBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
        String messageType = gcm.getMessageType(intent);
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
            // Get the message as a bundle.
            Bundle extras = intent.getExtras();
            // Get the command ID from the payload.
            String returnedID = extras.getString("commandID");
        }
    }
}

プッシュメッセージに含まれるペイロード intent.getExtras() から commandID を取得しています。
必要に応じてこのcommandID を利用して、コマンドリザルトの詳細を確認します。


try {
  // Get the command result.
  Command command = api.getCommand(returnedID);
  List<actionresult> results = command.getActionResults();
  for (ActionResult result : results) {
    String actionName = result.getActionName();
    boolean succeeded = result.succeeded();
    String errorMessage = result.getErrorMessage();
  }
} catch (ThingIFException e) {
  // Getting failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

5. トリガーの設定

以下のコードは、エアコンの例において、室温が30℃を超えたら、設定温度28℃、風量3で電源をONにするというトリガーを設定するコードです。


// Create a command
List<action> actions = new ArrayList<action>();
TurnPower action1 = new TurnPower();
action1.power = true;

SetPresetTemperature action2 = new SetPresetTemperature();
action2.presetTemperature = 28;

SetFanSpeed action3 = new SetFanSpeed();
action3.fanSpeed = 3;

actions.add(action1);
actions.add(action2);
actions.add(action3);

// Create a condition
Condition condition = new Condition(Range.greaterThanEquals("currentTemperature", 30));
// Create a predicate
StatePredicate predicate = new StatePredicate(condition, TriggersWhen.CONDITION_FALSE_TO_TRUE);

// Send a trigger
try {
  Trigger trigger = api.postNewTrigger("AirConditioner-Demo", 1, actions, predicate);
} catch (ThingIFException e) {
  // Posting failed for some reasons
  // Please check ThingIFException to see what went wrong...
}

まず、コマンドの送信と同様に、実行したいアクションの配列を作成します(// Create a command)。
次に、ステートの比較条件Conditionインスタンスを生成します(// Create a condition)。
ここでは、室温("currentTemperature"ステート)が、30℃以上(Range.greaterThanEquals)になったらという条件のインスタンスを生成しています。
さらに、トリガーの実行条件を指定します(// Create a predicate)。
ここでは、"前回の比較条件が false で、今回が true のときに実行"(TriggersWhen.CONDITION_FALSE_TO_TRUE)を指定しています。
最後に、Thing-IFの初期化で生成したThingIFAPI インスタンスによりトリガーを設定しています(api.postNewTrigger)。

ステートの比較条件やトリガーの実行条件は複数あります。また、条件を組み合わせることも可能です。
詳細はドキュメントを参照してください。

Kii Cloud

前の記事< | トップへ戻る | >次の記事

無料トライアル