THETAプラグインとIFTTTで時間記録【導入編】

# THETA プラグインと IFTTT で時間記録 【導入編】

## はじめに

こんにちは、リコーの@yomura\_です。

RICOH THETA のプラグインと IFTTT で簡易的な時間記録の仕組みを作ってみました。
今回は導入編です。

![theta_ifttt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381074/0cd3e5be-ea0f-0346-c454-b85e6b698a96.png)


## RICOH THETA プラグインについて

THETA プラグインをご存じない方は[こちら](https://api.ricoh/products/theta-plugin/)をご覧ください。
興味を持たれた方は[Twitter](https://twitter.com/thetaplugin)のフォローと [THETA プラグイン開発コミュニティ(Slack)](https://join.slack.com/t/thetadeveloper/shared_invite/enQtNzcxODI2MTk2ODg0LWY5YmU2ZTIxNGYwNzlmNWZjMWMyMWFlMzY3ZmQ3NGU3ZjZiZjI0Njk4MGIxMDU5NDJiMzQ4Y2FkNDEzYmFlNGU)への参加もよろしくお願いします｡

## IFTTT について

IFTTT は `IF This Then That` の形で外部サービス同士をつなぐサービスで、扱うサービスは SNS から IoT 機器まで様々です。
本記事では `This`をトリガー、`That`をアクションと呼び、この組み合わせのことをアプレットと呼んでいます。
今回は `Webhooks`サービスをトリガーとして `Google Sheets` に新規の行を追加するアクションを行うアプレットを作成しました。

### アプレットの作成

以下のような手順でアプレットを作成していきます。

- https://ifttt.com/create から新規アプレットを作成
- this(トリガー) に `Webhooks` を選択
  - `Receive a web request` を選択して`Event Name`を入力
    - 今回は `theta_button_pressed` としました (この名前は後ほど使います)
- that(アクション) に `Google Sheets` を選択
  - `Add row to spreadsheet` を選択して各項目を入力 (`Formatted row` については後述)

この作業で、指定の URL に POST するとスプレッドシートに 1 行追加するアプレットの作成が完了しました。

<img width="400" alt="ifttt_applet.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381074/43d8d877-141f-fda7-28e8-d80c33fea3ce.png">


### Formatted row について

記録する行のフォーマットを変更することができます。
また、下記 5 つを変数として埋め込むができます。

- `OccurredAt`: イベントの発生時刻 (今回はこの時間を記録するのが目的です)
- `EventName`: Webhook トリガー作成時のイベント名
- `Value1`: 任意の値
- `Value2`: 任意の値
- `Value3`: 任意の値

<img width="400" alt="formatted_row.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381074/e3d6e824-afae-0dee-1540-647b15a36635.png">


## 実装

今回は IFTTT アプレットのトリガーとなる URL に POST するためのタスクと、それを実行させる部分を実装しました。
このプラグインのサンプルソースコードは [GitHub](https://github.com/theta-skunkworks/theta-plugin-ifttt-sample) で公開しています。

### WebhookTask

<details><summary>WebhookTask.kt</summary><div>

```kotlin:WebhookTask.kt
package com.theta360.iftttsample.task

import android.util.Log

import com.google.gson.Gson
import com.google.gson.JsonObject
import okhttp3.*

import java.net.HttpURLConnection.HTTP_OK

class WebhookTask(private val url: String) {

    private val httpClient = OkHttpClient()

    fun request(): Boolean {

        val body = JsonObject().apply {
            addProperty("value1", "hoge")
            addProperty("value2", "fuga")
            addProperty("value3", "piyo")
        }

        val requestBody = RequestBody.create(
                MediaType.parse("application/json; charset=UTF-8"), GSON.toJson(body))
        val request = Request.Builder()
                .url(url)
                .post(requestBody)
                .addHeader("Accept", "application/json")
                .addHeader("X-XSRF-Protected", "1")
                .build()

        Log.d(TAG, "START - http request")
        val response = httpClient.newCall(request).execute()
        Log.d(TAG, "FINISH - http request")

        Log.d(TAG, "Status code: ${response.code()}")
        val responseBody = response.body()
        if (responseBody != null)
            Log.d(TAG, responseBody.string())
        return response.code() == HTTP_OK
    }

    companion object {
        private val TAG: String = WebhookTask::class.java.simpleName
        private val GSON = Gson()
    }
}
```

</div></details>

#### 任意文字列の追加

リクエストのボディに JSON で `value1`,`value2`,`value3` キーに文字列を指定することで、スプレッドシートに任意の文字列を記録できます。
今回は良い使い方が思いつかなかったのでこんな感じになっています。

```kotlin
val body = JsonObject().apply {
    addProperty("value1", "hoge")
    addProperty("value2", "fuga")
    addProperty("value3", "piyo")
}
```

### MainActivity

<details><summary>MainActivity.java</summary><div>

```java:MainActivity.java
package com.theta360.iftttsample;

import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import com.theta360.iftttsample.task.TakePictureTask;
import com.theta360.iftttsample.task.TakePictureTask.Callback;
import com.theta360.iftttsample.task.WebhookTask;
import com.theta360.pluginlibrary.activity.PluginActivity;
import com.theta360.pluginlibrary.callback.KeyCallback;
import com.theta360.pluginlibrary.receiver.KeyReceiver;
import com.theta360.pluginlibrary.values.LedColor;
import com.theta360.pluginlibrary.values.LedTarget;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

public class MainActivity extends PluginActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private static final String WEBHOOK_KEY = "write ifttt webhook key here";
    private static final String WEBHOOK_EVENT_NAME = "write ifttt webhook event name here";
    private static final String WEBHOOK_URL = "https://maker.ifttt.com/trigger/" + WEBHOOK_EVENT_NAME + "/with/key/" + WEBHOOK_KEY;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private AtomicBoolean isProceeding = new AtomicBoolean();
    private WebhookTask webhookTask = new WebhookTask(WEBHOOK_URL);

    private TakePictureTask.Callback mTakePictureTaskCallback = new Callback() {
        @Override
        public void onTakePicture(String fileUrl) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Set enable to close by pluginlibrary, If you set false, please call close() after finishing your end processing.
        setAutoClose(true);
        // Set a callback when a button operation event is acquired.
        setKeyCallback(new KeyCallback() {
            @Override
            public void onKeyDown(int keyCode, KeyEvent event) {
                if (keyCode == KeyReceiver.KEYCODE_CAMERA) {
                    /*
                     * To take a static picture, use the takePicture method.
                     * You can receive a fileUrl of the static picture in the callback.
                     */
//                    new TakePictureTask(mTakePictureTaskCallback).execute();
                    if (isProceeding.get()) {
                        Log.d(TAG, "Skip to post webhook.");
                        return;
                    }
                    executor.submit(() -> {
                        isProceeding.set(true);
                        notificationAudioOpen();
                        try {
                            if (webhookTask.request()) {
                                Log.d(TAG, "Succeeded to post webhook.");
                                notificationAudioClose();
                            }
                        } catch (Exception e) {
                            Log.e(TAG, "Failed to post webhook.", e);
                            notificationAudioWarning();
                        } finally {
                            isProceeding.set(false);
                        }
                    });
                }
            }

            @Override
            public void onKeyUp(int keyCode, KeyEvent event) {
                /**
                 * You can control the LED of the camera.
                 * It is possible to change the way of lighting, the cycle of blinking, the color of light emission.
                 * Light emitting color can be changed only LED3.
                 */
                notificationLedBlink(LedTarget.LED3, LedColor.BLUE, 1000);
            }

            @Override
            public void onKeyLongPress(int keyCode, KeyEvent event) {

            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (isApConnected()) {

        }
    }

    @Override
    protected void onPause() {
        // Do end processing
        //close();

        super.onPause();
    }
}

```

</div></details>

#### Webhook キーとイベント名の設定

IFTTT の Webhook キーと Webhook イベント名を URL に含める必要があります。
`WEBHOOK_EVENT_NAME` にはアプレット作成時に設定した `Event Name` を入力します。
`WEBHOOK_KEY` は https://ifttt.com/maker_webhooks の `Documentation` から確認できます。

```java
private static final String WEBHOOK_KEY = "write ifttt webhook key here";
private static final String WEBHOOK_EVENT_NAME = "write ifttt webhook event name here";
```

#### タスクの実行

非同期処理には `Executors.newSingleThreadExecutor()` を使いました。
この`executor`で別スレッドを生成して非同期処理を実行させています。

```java
import java.util.concurrent.ExecutorService;

public class MainActivity extends PluginActivity {
     private ExecutorService executor = Executors.newSingleThreadExecutor();
     ...
```

作成した`WebhookTask`は `onKeyDown` メソッド内で実行しています。
これで THETA の撮影ボタンを押した時に指定の URL に POST リクエストを送信しています。

```java
executor.submit(() -> {
    isProceeding.set(true);
    notificationAudioOpen();
    try {
        if (webhookTask.request()) {
            Log.d(TAG, "Succeeded to post webhook.");
            notificationAudioClose();
        }
    } catch (Exception e) {
        Log.e(TAG, "Failed to post webhook.", e);
        notificationAudioWarning();
    } finally {
        isProceeding.set(false);
    }
});
```

#### タスク実行中のチェック

AtomicBoolean 型の変数 `isProceeding` を処理中であることを示すフラグとして使用しています。これにより、タスク完了前に `KEYCODE_CAMERA` イベントが発生した場合はスキップしています。

```java
if (keyCode == KeyReceiver.KEYCODE_CAMERA) {
    if (isProceeding.get()) {
        Log.d(TAG, "Skip to post webhook.");
        return;
    }
    ...
```

## 実行結果

THETA プラグインを起動して撮影ボタンを押すとスプレッドシートに 1 行追加されました。

<img width="872" alt="result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381074/01b9d852-23ed-6d78-deb4-a840e6d19857.png">


## おわりに

今回は THETA のボタンを直接押してスプレッドシートに記録するところまでを紹介しました。
次回はボタンを直接押す以外の方法で記録する方法にチャレンジしてみたいです。

### 応用 1: OpenCV との組み合わせ

THETA で画像処理ライブラリの OpenCV が使えるので、人の動きを検知してスプレッドシートに記録することができれば便利そうです。
動体検知したタイミングで撮影して、人物の認識までできれば簡単な入退室管理ができるかもしれません。

[THETA の中で OpenCV を動かす【プレビューフレーム応用編】](https://qiita.com/roohii_3/items/75c3e73cee0ed6d53b4b)

### 応用 2: BLE ボタンとの組み合わせ

THETA プラグインで BLE (Bluetooth Low Energy) の電波を受信することができます。
スマートフォンが近づくと記録するなど、いろいろな活用ができそうです。

[THETA プラグインで 市販の BLE ボタンをリモコンにしてみた](https://qiita.com/shrhdk_/items/49963abf100303e65ac2)