犬用遠隔おやつあげ機の製作 - Kii Cloud編

Kii Cloud を使って外出先から犬におやつをあげる装置を作ったので紹介します。

何はともあれ、次の動画をご覧ください。スマートフォンで操作すると、犬におやつを 1 粒あげることができます。装置はインターネットでつながっているので、外出先からも操作できます。

おやつあげ機には、小さな皿がついたベルトコンベアがあります。スマートフォンからインターネット経由で指示があると、モーターで 1 皿分を回転させて、おやつを犬の元に届けます。

Kii Cloud

スマートフォンと装置は、Kii Cloud の新 API 「Thing Interaction framework(Thing-IF)」を使って連携させました。ステマになるといけないので書いておきますと、私は Kii の中の人で、普段はマニュアルを担当しています。

今回の目的はデモの製作ではなく、犬のリモート監視ソリューションを本当に作りたくて作りました。趣味での開発なので、Kii Cloud を使わずにサーバ側は独自実装にしても良かったのですが、プッシュ通知まですべて実装するのは大変です。完成後の運用まで考えると Kii Cloud が最適という判断でした。主要機能の開発は 2 日もあれば十分、かつ、利用時にも安定していますので、Kii Cloud の採用は正解でした。

なお、現在運用中のシステムのソースコードは github で近日中に公開します。

今回のブログでは、連携のベースとなっている Kii Cloud の Thing-IF を中心に情報をまとめてみました。ハードウェアの製作過程や装置の全体像は別のページでまとめていきます。

仕組み

おやつあげ機の構成は図の通りです。

Android スマートフォンとおやつあげ機をインターネット経由で Kii Cloud / Thing-IF につなげます。おやつあげ機のモーターやベルトコンベアは、Windows PC をゲートウェイにして、Arduino をデバイス制御のマイコンとしてつないでいます。

IoT(Internet of Things) を実現する場合は小型の専用装置を使用するのが一般的ですが、今回は別の機能で Web カメラ 3 台と USB スピーカ-も同時につなげたかったため、低電力の Windows PC をゲートウェイにしてすることにしました。

構成上、おやつが出るまでの仕組みはこんな感じです。

  1. 準備として、スマートフォンと Windows PC をそれぞれ Thing-IF(Kii Cloud)に登録しておきます。Thing-IF から見ると、Windows PC は IoT デバイスとして見えます。これによって、スマートフォンから Windows PC を制御できます。
  2. おやつをあげたいとき、スマートフォンの専用アプリから 「おやつあげコマンド」を Kii Cloud に送信します。
  3. Kii Cloud は、スマートフォンに紐付けられた IoT デバイス(Windows PC)にコマンドを送ります。
  4. Windows PC は受け取ったコマンドを解釈して、COM ポート経由で Arduino にコマンドを中継します。
  5. Arduino はベルトコンベア上の皿を 1 皿分動かすようにステッピングモーターを制御します。

以下、1 ~ 3 の実装方法について紹介します。

Thing-IF

まずは、今回のコアとなる技術「Thing-IF」について簡単に説明しておきます。

Thing-IF とは Kii Cloud 上で動作する IoT 用のフレームワークです。Thing-IF では、Kii Cloud が提供している様々な「機能要素」の API を内部で組み合わせて、IoT で要求されるベストプラクティスのシナリオに合うよう、フレームワーク化されています。そのため、これらのシナリオと一致する使い方であれば、目的の機能を素早く実現することができます。

Thing-IF で想定されているシナリオは以下の 3 つです。

  • スマートフォンからコマンドを送って、IoT デバイス側でそれを受けて何かする。
  • IoT デバイス側から状態を表す JSON(「ステート」と呼んでいます)をアップロードして、スマートフォンからそれを参照する。
  • コマンドをあらかじめ登録しておき、特定の条件が満たされたときにそれを自動実行する。

スマートフォンからおやつをあげるというシナリオは、1 番目のモデルに一致しているため、Thing-IF が使えます。

なお、これらのシナリオに合わないケースで無理に Thing-IF を使ってもうまくいきません。シナリオに合わない場合は、Kii Cloud SDK(Thing SDK)を直接使って機能を実現できます。たとえば IoT で犬用トイレ使用中センサーの製作 は、3 つのシナリオのどれにも一致しない使い方をするため、Thing-IF を使わずに機能を実現しています。

コマンドのやりとり

まずはコマンドを設計します(Thing-IF では「スキーマ」と呼んでいます)。

Thing-IF を通しておやつをあげるとき、スマートフォンから IoT デバイスに「コマンド」を送信します。コマンドは、アプリの仕様に合わせて、名前やパラメータをカスタマイズできます。

今回の場合、「おやつあげコマンド」として、SnackAction という名前のコマンドを投げることにしました。Thing-IF ではコマンドは複数のアクションの集まりで、各アクションは JSON 形式のパラメータを持つことができます。今回はコマンドを送ったかどうかが分かればよいため、パラメータを持たない(空の JSON)単一のアクションをコマンドとします。

以下は実行のイメージです。SnackAction をスマートフォンから投げると、Thing-IF が IoT デバイスにそれを送り届けてくれます。

Android SDK では Java のクラス表現でコマンドを定義することになっています。次のクラスを定義します。

public class SnackAction extends Action {
    @Override
    public String getActionName() {
        return "SnackAction";
    }
}

パラメータがないので、空のクラスを定義して、アクション名を返すメソッドだけを定義しています。

スマートフォン側の実装

スマートフォン側では、初期登録を行った後、コマンドを送信します。

公式ドキュメントの Android アプリ開発ガイド に従って実装していきます。

初期化

Thing-IF を使うには初期化処理が必要です。公式ドキュメントからコピー&ペーストを繰り返すことで実装できます。

github のソースコードから今回の機能範囲の初期化コードを抜粋すると、次のようになります。

ThingIFAPI mApi;

Kii.initialize(mContext, config.getAppID(), config.getAppKey(), config.getSiteUrl() + "api/", false);
KiiUser.logIn("aaaa", "bbbb");

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

SchemaBuilder sb = SchemaBuilder.newSchemaBuilder("WankoWatcher", "WankoWatcher",
        1, WankoWatcherState.class);
sb.addActionClass(SnackAction.class, SnackActionResult.class);
Schema schema = sb.build();

ThingIFAPIBuilder ib = ThingIFAPIBuilder.newBuilder(mContext,
        config.getAppID(), config.getAppKey(), config.getSiteUrl(), owner);
ib.addSchema(schema);
mApi = ib.build();

// initialize push notification
mGcm = GoogleCloudMessaging.getInstance(mContext);
String regId = mGcm.register("638386635756");

mApi.installPush(regId, PushBackend.GCM);

// onboarding
JSONObject properties = new JSONObject();
mApi.onboard(config.getThingVendorId(), config.getThingPassword(), "WankoWatcher", properties);

実行している処理は次のような感じです。作業スレッドを作ってブロッキング API で実装しています。

  • Kii Cloud SDK を初期化
  • 固定のユーザー名とパスワードでオーナーユーザーをログイン
  • ログイン中のユーザーのアクセストークンとユーザー ID を取り出して、Thing-IF で使うオーナーユーザーを定義
  • 使用するコマンドのアクションクラス SnackAction と、ダミーのステートクラス WankoWatcherState を用意し、スキーマを登録
  • Kii Cloud のアプリケーションの AppID、AppKey、オーナー、スキーマを指定して初期化
  • プッシュ通知の機能を初期化
  • IoT デバイスの ID とパスワードを指定して初期登録を実行

初期化が完了すると、ThingIFAPI クラスのインスタンスを入手できます。これは、その名の通り、API を実行できるインスタンスです。後でコマンドを送信するときに必要となるので、クラスのフィールドなどで保存しておきます。

最後の初期登録(onbord)では、スマートフォンに紐付ける IoT デバイスの ID とパスワードを指定します。これによって、スマートフォンと Windows(IoT デバイス)が紐付いて、スマートフォンからのコマンドが Windows に届きます。Windows 側の実装では、初期化の際に同じ ID を指定します。

コマンドの送信

次はコマンドの送信部分の実装です。

モバイルアプリでおやつコマンドの送信ボタンが押されると、通常通り OnClick ハンドラが呼ばれます。この処理で、Thing-IF に SnackAction コマンドを送信します。

ここでは、先ほどの SnackAction のインスタンスを作成してアクション配列として送信します(1 つのアクションから構成されるコマンドを送信)。初期化で取得した mApi の postNewCommand() を呼び出すと、Kii Cloud が IoT デバイスにコマンドを送り届けてくれます。

送信部分は次の通りです。

List<Action> actions = new ArrayList<Action>();SnackAction action1 = new SnackAction();
actions.add(action1);
mApi.postNewCommand("WankoWatcher", 1, actions);

これだけのコードでコマンドを IoT デバイスに送信できるので、非常に簡単です。とは言っても、Android でそれなりのアプリにしようとすると、UI のデザインとか、Activity の再起動対策とか、作業が山積みになるのはいつものことなのでした…。

Windows 側の実装

IoT デバイス側も、公式ドキュメントの Thing 開発ガイド に従って作り込みます。

こちらはリファレンス実装が用意されているので、スマートフォン側よりも簡単です。github にスケルトンとなるコードがあるので、それをベースに改造すれば目的の機能をすぐに実現できます。

ただし、今回は動作環境が SDK の想定環境(組み込み linux)と異なるため、Windows への移植が必要でした。これに関しては別のページで紹介します。

Thing-IF SDK は、「初期化処理とハンドラだけを用意すればすぐに使える」というコンセプトで実装されています。サンプルコードに対して、次の 3 つの書き換えを行えば基本的な部分ができあがります。

  1. 初期化の修正
  2. アクションハンドラの修正
  3. ステートハンドラの修正

それぞれ、詳細をご紹介します。

初期化の修正

IoT デバイスは Windows PC のため、Windows サービス(デーモンプロセス)として作りたいところですが、今回は単純なコンソールアプリとしました。

初期化処理を抜き出して、目的のプログラムの main() 関数につなげます。今回はおやつサーバ以外にもいろいろな機能を作り込みたいので、自分の設計に合わせて ThingInteractionMain::Initialize() のような C++ メソッドに初期化処理を移植しました。

初期化処理は次のような感じです。

int option_index = 0;
kii_bool_t result;

m_command_handler_resource.buffer = m_command_handler_buff;
m_command_handler_resource.buffer_size = sizeof(m_command_handler_buff) / sizeof(m_command_handler_buff[0]);
m_command_handler_resource.mqtt_buffer = m_mqtt_buff;
m_command_handler_resource.mqtt_buffer_size = sizeof(m_mqtt_buff) / sizeof(m_mqtt_buff[0]);
m_command_handler_resource.action_handler = action_handler;
m_command_handler_resource.state_handler = state_handler;

m_state_updater_resource.buffer = m_state_updater_buff;
m_state_updater_resource.buffer_size = sizeof(m_state_updater_buff) / sizeof(m_state_updater_buff[0]);
m_state_updater_resource.period = STATE_UPDATE_PERIOD;
m_state_updater_resource.state_handler = state_handler;

// initialize
Configuration* config = Configuration::GetInstance();
result = init_kii_thing_if(&m_kii_iot, config->GetAppID().c_str(), config->GetAppKey().c_str(), config->GetAppSite().c_str(),
    &m_command_handler_resource, &m_state_updater_resource, NULL);
if (result == KII_FALSE) {
  return FALSE;
}

result = onboard_with_vendor_thing_id(&m_kii_iot,
    config->GetVendorThingID().c_str(), config->GetVendorThingPassword().c_str(), "PC", NULL);
if (result == KII_FALSE) {
  return FALSE;
}

少し長いですが、ほぼ公式ドキュメントのコピーです。

初期化の際、action_handler でアクションハンドラの関数ポインタを、state_handler でステート登録処理の関数ポインタを指定します。これらの関数の実体は後で修正します。

その他、重要な部分は、最後に指定している vendorThingID とパスワードです。これらをスマートフォン側のものと同じにすることで、紐付けを行うことができます。

アクションハンドラの修正

アクションハンドラは、コマンドを受け取ったときに実行する処理です。リファレンス実装から実装本体を一度削除して、次のように実装します。

#define ACTION_NAME_SNACK "SnackAction"
kii_bool_t action_handler(const char* schema, int schema_version, const char* action_name, const char* action_params, char error[EMESSAGE_SIZE + 1])
{
  if (strcmp(action_name, ACTION_NAME_SNACK) == 0) {
    ThingInteractionMain::GetInstance()->OnActionSnack(action_params, error);
  } else {
    strcpy(error, "Unknown Command");
    return KII_FALSE;
  }
  return KII_TRUE;
}

スマートフォンからのコマンドを受信すると、SDK は action_handler として登録した関数を呼び出します。このとき、引数にアクション名と、パラメータの JSON 文字列(今回は常に "{}")が渡ってくるので、それを解析して目的の機能を実現します。

おやつコマンドの場合は、コマンドが来たことだけが分かればいいので、アクション名だけを確認して、実処理 OnActionSnack に飛びます。action_name に入っている文字列は、初めに示した Javaクラスの SnackAction#getActionName() が返した文字列 "SnackAction" です。

OnActionSnack では、COM ポートを経由して Arduino と通信してモーターを駆動する処理を実装しています。この処理の詳細は、Arduino 側の実装として別のページで紹介します。

ステートハンドラの修正

ステートハンドラには、デバイスの状態を Thing-IF にアップロードする処理を記述します。今回はステートを使用しないため、ダミーの実装としています。

kii_bool_t state_handler(
    kii_t* kii,
    KII_THING_IF_WRITER writer)
{
  if ((*writer)(kii, "{}") == KII_FALSE) {
    return KII_FALSE;
  }
  return KII_TRUE;
}

まとめ

以上が、おやつあげ機の根幹部分となるプログラムの実装です。

スマートフォン側、Windows PC 側のいずれも、初期化処理の後、コマンドの送信処理や受信処理を書いただけです。コマンドのやりとりなどの面倒な部分はすべて Thing-IF がやってくれます。

特に、IoT デバイス側のプッシュ通知の実装が容易にできるのは魅力だと思います。Thing-IF では、コマンドの受信があったことを MQTT プロトコルの Publish コマンドの受信で検知し、自動的にアクションハンドラが呼び出される仕組みです。これを初めから実装するのはかなり骨が折れる仕事です。SDK が面倒な部分をうまく隠しているため、プログラム側では実装量が非常に少なくてすみます。IoT のプロトタイプを製作したい場合には最適な環境なのではないでしょうか。

次回はおやつあげ機と同時に実装されている他の機能についても紹介していきます。