AndroidとSesameとNFCで開けゴマ WEBAPI版

修正(2020/05/12):CradIDの型を指定、カードIDの等価式を修正

#目的
家の鍵を「かざ」~~すだけで~~して開けたい！

・Sesame
・Sesame Wifiアクセスポイント
https://jp.candyhouse.co

・NFCカード + 電波遮断シート
　玄関のドアは金属性なので、台紙にしないと反応しないので
　（100均に電波遮断シートが売ってることにびっくりした、、、
・Androidスマホ

##方法
１．AndroidでNFCを読み込み→ネット→公開APIで解錠（本記事）
![WEBAPI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602722/ba4880f1-b4b9-1b9e-9461-2d346094c79c.png)
２．AndroidでNFCを読み込み→BLEで解錠([別記事](https://qiita.com/sakujira/items/93998534d8358b387ee9))
![BLE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602722/e8b94aa3-4976-0ad3-9d99-48f89b928328.png)

##成果物
事前準備として、
NXP TagWriter
https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter&hl=ja
にて、書込み可能なタグに対して
　「Plain Text」で適当な文字列
を書き込んで置いてください。

AndroidManifest.xml

	<?xml version="1.0" encoding="utf-8"?>
	<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	    package="com.example.myapplication">

	    <!-- APP起動に必要な権限 -->
	    <uses-permission android:name="android.permission.NFC" />
	    <uses-permission android:name="android.permission.INTERNET" />
	    <application
	        android:allowBackup="true"
	        android:icon="@mipmap/ic_launcher"
	        android:label="@string/app_name"
	        android:roundIcon="@mipmap/ic_launcher_round"
	        android:supportsRtl="true"
	        android:theme="@style/AppTheme">
	        <activity android:name=".MainActivity">
	            <intent-filter>
	                <action android:name="android.intent.action.MAIN" />
	                <category android:name="android.intent.category.LAUNCHER" />
	            </intent-filter>
	        </activity>
	        <activity-alias
	            android:name=".MainActivityAlias"
	            android:enabled="true"
	            android:targetActivity=".MainActivity">

	            <!-- NFC card からの起動 -->
	            <intent-filter>
	                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
	                <category android:name="android.intent.category.DEFAULT" />
	                <data android:mimeType="text/plain" />
	            </intent-filter>
	        </activity-alias>
	    </application>
	</manifest>

kotlinx.coroutinesとimport com.github.kittinunf.fuelを使うので、
buid.gradle(app)に以下を追加

	dependencies {
	・・・・
	   implementation 'com.github.kittinunf.fuel:fuel:X.X.X'
	   implementation "com.github.kittinunf.fuel:fuel-json:X.X.X"
	   implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:X.X.X'
	}

MainActivity.kt

	package com.example.myapplication

	//AndroidでUIを使うためのおまじない
	import android.content.pm.PackageManager
	import android.os.Bundle
	import androidx.appcompat.app.AppCompatActivity
	//NFCを使うためのライブラリ
	import android.nfc.NfcAdapter
	//HTTP(S)をメインスレッドで呼び出さないためのライブラリ
	import kotlinx.coroutines.*
	//WebAPIを実行するためのライブラリ
	import com.github.kittinunf.fuel.httpGet
	import com.github.kittinunf.fuel.httpPost
	import com.github.kittinunf.fuel.json.responseJson
	import com.github.kittinunf.result.Result

	class MainActivity : AppCompatActivity() {
	    //鍵にしたいカードのIDを情報
	    val CardID : String = -鍵にしたいCardID-
	    //操作したいSesameのAPI情報
	    val AuthCode = -APIの鍵-
	    val DeviceID = -APIに使うDEVICEID-

	    override fun onCreate(savedInstanceState: Bundle?) {
	        super.onCreate(savedInstanceState)
	        setContentView(R.layout.activity_main)

	        //このアプリを開いた[起因]のNFC情報を読み取る
	        //[起因]は、AndroidManifest.xmlに規定した<intent-filter>に起因する
	        if(NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
	            //カードのID情報を取得
	            val tagId : ByteArray = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID)
	            val list = ArrayList<String>()
	            for(byte in tagId) {
	                list.add(String.format("%02X", byte.toInt() and 0xFF))
	            }
	            //読み込んだカードIDが一致すれば
	            if(list.joinToString(separator = ":") == CardID)){

	                //Coroutineで別スレッドにてHTTP(S)処理
	                GlobalScope.launch{
	                    Change_Status()
	                }
	            }
	        }
	    }

	    /**
	     * Sesame Web API関係
	     */
	    fun Change_Status(){
	        //SesameのAPIに従って、HTTP(S)のheaderにApiキーを設定
	        val header1: HashMap<String, String> = hashMapOf("Authorization" to AuthCode)
	        //一回目の通信で、操作したSesameの状態を取得し記録
	        var Locked : Boolean = false
	        var Responsive : Boolean = false

	        //一回目の通信：Sesameの状態を取得（同期通信）
	        val (_, _, result1) = ("https://api.candyhouse.co/public/sesame/"+DeviceID).httpGet().header(header1).responseJson()
	        when (result1){
	            //通信失敗時：
	            is Result.Failure -> {
	            }
	            //通信成功時：結果としてJsonが返ってくるので
	            is Result.Success -> {
	                //施錠状態を取得
	                if (result1.get().obj().get("locked").toString() == "true"){
	                    Locked = true
	                }
	                //現在操作可能かを取得
	                if (result1.get().obj().get("responsive").toString() == "true"){
	                    Responsive = true
	                }
	            }
	        }

	        //操作可能であれば、2回目の通信
	        if(Responsive){
	            //SesameのAPIに従って、HTTP(S)のheaderにApiキーと、POSTで送るデータ型を設定
	            val header2: HashMap<String, String> = hashMapOf("Authorization" to AuthCode, "Content-Type" to "application/json")
	            //Seameに対する操作を設定 状態を逆する様に設定
	            var command: String = """{"command":"lock"}"""
	            if(Locked){command = """{"command":"unlock"}"""}

	            //二回目の通信：Sesameに対して指令（同期通信）
	            val (_, _, result2) = ("https://api.candyhouse.co/public/sesame/"+DeviceID).httpPost().header(header2).body(command).responseString()
	            //三回目の通信をして、コマンドが処理されたかを確認するのは、
	            //目の前で鍵が閉まった、開いたかを確認するのでこのアプリではチェックしない
	            when (result2){
	                is Result.Failure -> {
	                }
	                is Result.Success -> {
	                }
	            }
	        }
	    }
	}


##動作フロー
・スマホにNFCタグをかざす
A.Androidのタグディスパッチシステムにより、アプリが起動
A.onCreate内でそのままIDを読み込み
・NFCからWEBPIへ操作が移り変わる
B.状態の取得
B.施錠・解錠の変更

##参考文献
####WebAPIに関した部分
Sesame API - CANDY HOUSE, Inc.
https://docs.candyhouse.co

APIキー取得方法とセサミIDの確認方法
https://jp.candyhouse.co/blogs/how-to/apiキー取得方法とセサミidの確認方法

言わずもがな、公式サイトのAPI情報を参照

#####AndroidでHTTP通信する
KotlinでHTTP通信（FuelとHttpURLConnection）
https://qiita.com/naoi/items/8df1409ad48ad8f3c632

Kotlin/FuelでHTTPアクセスしました。
https://qiita.com/naokiur/items/d6337bfc976cb1249575

Kotlin+fuel-coroutinesを使ってHTTP通信
https://qiita.com/chouxcreams/items/abb032d8127098dea7d1

Kotlin の Coroutine を概観する
https://qiita.com/kawmra/items/ee4acb7db61f70dec9a8

メインスレッドにてHTTP(S)通信が出来ないようになっていることで、マルチスレッドをどうしたらいいか四苦八苦。
状態確認→施錠・解錠処理は連動させたかったため、連続する動作は「同期処理」にして、全体の処理を「非同期」にすることにしました。
HTTP(S)通信については色んなライブラリ、色んな書き方があったので、むしろその差を理解する方が大変だったなっと思っています。

##編集後記
BLE版を見てから思うと、アッサリとしたソースだなぁとしみじみ思います。