SESAMEをUnityから操作する

スマホから操作できるスマートロック SESAME(セサミ) は API が用意されているため、Unity から操作してみます。

SESAME の詳細は下記を参照しましょう。
https://candyhouse.co/
https://ameblo.jp/candyhouse-inc/entry-12332356099.html

スマートロックだと Qrio 等が有名ですが、現時点で API が公開されているもので日本から直接購入できるのはこれだけのような気がします。

また、前提として下記を済ませておきましょう。
・SESAME 及び WiFi アクセスポイントを設置
・スマホアプリから連携し、パスワードを変更
・操作したい SESAME の設定から "クラウド" を有効に

注意が必要なのがアクセスポイント設置とパスワードの変更です。
アクセスポイントはスマホで現在使用している WiFi としか紐づけをできず、アクセスポイントの端末は 2.4GHz の回線としか繋げません。
そして Google 連携で使用し始めた場合、Google のパスワードでは当然認証がうまく行きません。

さて、API は[こちら](https://docs.candyhouse.co/)に公開されています。
ログイン処理を行うと操作に必要なトークンが発行されますが、そちらは30日で失効するようです。
どのように使うかにもよるでしょうが、今回は都度ログインする仕様にすることにします。

以下実際のコードを貼りますが、やってることはこんな感じ
1. ログインしてトークン取得 (=SetupCor)
2. トークンで API による操作を許可している SESAME 一覧を取得 (=SetupCor)
3. トークン及び SESAME の ID で操作を行う (=UnlockCor, LockCor, ToggleCor)

```SesameController.cs

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class SesameController : MonoBehaviour {

    [System.Serializable]
    private class LoginInfo
    {
        public string email = "登録してあるメールアドレス";
        public string password = "パスワード";
    }

    [System.Serializable]
    private class AuthorizationResponse
    {
        public string authorization = "";
    }

    [System.Serializable]
    private class SesameInfo
    {
        public string device_id = "";
        public string nickname = "";
        public bool is_unlocked = false;
        public bool api_enabled = false;
        public int battery = 0;
    }

    [System.Serializable]
    private class Sesames
    {
        public SesameInfo[] sesames = null;
    }

    [System.Serializable]
    private class UnlockCommand
    {
        public string type = "unlock";
    }

    [System.Serializable]
    private class LockCommand
    {
        public string type = "lock";
    }

    private string authToken = "";
    private Sesames sesameList = null;

    // ----- Unity Callback -----
    
    // Use this for initialization
    IEnumerator Start () {
        yield return StartCoroutine(SetupCor());
    }

    // ----- Public Methods -----


    // ----- Setup -----
    public IEnumerator SetupCor()
    {
        // login
        var loginReq = new UnityWebRequest("https://api.candyhouse.co/v1/accounts/login", "POST");

        var body = JsonUtility.ToJson(new LoginInfo());
        byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(body);

        loginReq.uploadHandler = new UploadHandlerRaw(bodyRaw);
        loginReq.downloadHandler = new DownloadHandlerBuffer();

        loginReq.SetRequestHeader("Content-Type", "application/json");

        yield return loginReq.SendWebRequest();

        if (loginReq.isNetworkError || loginReq.isHttpError)
        {
            Debug.LogError(loginReq.error);
        }
        else
        {
            Debug.Log("auth ok");
            authToken = JsonUtility.FromJson<AuthorizationResponse>(loginReq.downloadHandler.text).authorization;
        }

        loginReq.Dispose();

        // get sesame list all
        var getSLReq = UnityWebRequest.Get("https://api.candyhouse.co/v1/sesames");
        getSLReq.SetRequestHeader("X-Authorization", authToken);

        yield return getSLReq.SendWebRequest();

        if (getSLReq.isNetworkError || getSLReq.isHttpError)
        {
            Debug.LogError(getSLReq.error);
        }
        else
        {
            // convert response json to class.
            sesameList = JsonUtility.FromJson<Sesames>(getSLReq.downloadHandler.text);
        }

        getSLReq.Dispose();
    }


    // ----- Controll sesame status -----
    public IEnumerator UnlockCor(int index)
    {
        yield return controllCor(index, true);
    }

    public IEnumerator LockCor(int index)
    {
        yield return controllCor(index, false);
    }

    public IEnumerator ToggleCor(int index)
    {
        if (authToken == "")
        {
            Debug.LogError("setup first");
            yield break;
        }

        yield return controllCor(index, !sesameList.sesames[index].is_unlocked);
    }

    IEnumerator controllCor(int index, bool unlock)
    {
        if (authToken == "")
        {
            Debug.LogError("setup first");
            yield break;
        }

        var controllUri = string.Format("https://api.candyhouse.co/v1/sesames/{0}/control", sesameList.sesames[index].device_id);
        var controllReq = new UnityWebRequest(controllUri, "POST");

        // create body by command type
        var body = unlock ? JsonUtility.ToJson(new UnlockCommand()) : JsonUtility.ToJson(new LockCommand());
        byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(body);

        controllReq.uploadHandler = new UploadHandlerRaw(bodyRaw);
        controllReq.downloadHandler = new DownloadHandlerBuffer();

        controllReq.SetRequestHeader("X-Authorization", authToken);
        controllReq.SetRequestHeader("Content-Type", "application/json");

        yield return controllReq.SendWebRequest();

        if (controllReq.isNetworkError || controllReq.isHttpError)
        {
            Debug.LogError(controllReq.error);
        }

        controllReq.Dispose();
    }
}
```

操作方法がロックアンロックのみで、トグルが用意されてなかったので自分で用意しました。それ以外は特に特別なことはやっていません。
なお、初回のみ操作してから反映に長めのラグが起こりました。初回は何か特別な認証がなされているのかもしれません。

上記の操作は GET POST を行っているだけなのでどの言語からでも可能ですが、例えばリアル脱出ゲームで参加者にデバイスを渡しておき、 AR でパズルを解いたら次の部屋への鍵が勝手に開くみたいな仕組みを作るのに使えそうですよね。そういった他のゲームとの組み合わせで何かするときに Unity と組み合わせるのは良いかなと思います。