修正(2020/05/12):CradIDの型を指定、カードIDの等価式を修正
#目的
家の鍵を「かざ」すだけでして開けたい!
・Sesame
・Sesame Wifiアクセスポイント
https://jp.candyhouse.co
・NFCカード + 電波遮断シート
玄関のドアは金属性なので、台紙にしないと反応しないので
(100均に電波遮断シートが売ってることにびっくりした、、、
・Androidスマホ
##方法
1.AndroidでNFCを読み込み→ネット→公開APIで解錠(本記事)
2.AndroidでNFCを読み込み→BLEで解錠(別記事)
##成果物
事前準備として、
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版を見てから思うと、アッサリとしたソースだなぁとしみじみ思います。