NFC・Felica(Suica, PASMO)でSESAMEを解錠出来るようにする

# 注意
この記事は `nfcpy` が `Python2` にしか対応していなかった頃に実装した記録です。
現在、 `nfcpy` は `Python3` に対応しています。

## 環境変数について

私は環境変数を使う形で書いたのですが、一般ユーザーで環境変数を設定した後
raspbian では sudo すると環境変数がリセットされてしまうので、
sudo 時に環境変数を引き継ぐようにするか sudo 無しで使えるようにする必要がありました。

### `sudo` 無しでRC-S380を使える様にする

参考: [Suicaでセサミを解錠 ( with ラズパイ）](https://ameblo.jp/candyhouse-inc/entry-12413908790.html)
参考: [交通系電子マネーカードを 黒PaSori RC-S380 + Raspberry Pi + python で](https://qiita.com/xshell/items/55302a588b5927dde6b6#%E5%8F%82%E8%80%83%E3%82%B5%E3%82%A4%E3%83%88)

```
> lsusb

> sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'
> sudo udevadm control --reload-rules
> sudo reboot
```

`ATTRS{idVendor}==\"XXXX\", ATTRS{idProduct}==\"YYYY\"` の部分は使っているカードリーダーによって固定の値です。
Sony の PaSori RC-S380 なら上記のコピペで平気なはずです。
参考: [USBのベンダーIDとプロダクトIDの話](https://qiita.com/gpsnmeajp/items/8eb8ecf0541032f6de0e#%E3%83%99%E3%83%B3%E3%83%80%E3%83%BCid%E3%83%AA%E3%82%B9%E3%83%88)
違うリーダーの場合 `lsusb` の値を参照しましょう。

### sudo 時に環境変数を引き継ぐようにする場合

こちらの記事を参考にしました
参考: [sudo時に環境変数PATHが引き継がれない時の対策](http://note.kurodigi.com/sudo-path/)
上記記事では `PATH` に限定していますが、 PATH 以外の環境変数を引き継ぎたい場合も `visudo` で同様にできます

root になってから `visudo` を実施します

```
$ su -
# visudo
```

`env_reset` と `secure_path` をコメントアウト(先頭に `#` を足)します
そして、次の一行を追加します

```
Defaults	env_keep += "<引き継ぎたい環境変数>"
```

と `env_keep` に加えることで引き継がせることが出来ます。

# 実装

これらの記事を参考にしました
参考: [Suicaでセサミを解錠 ( with ラズパイ）](https://ameblo.jp/candyhouse-inc/entry-12413908790.html)
参考: [PaSoRiでNFCタグとFeliCaを読み込んで出力](https://qiita.com/nofrmm/items/96eba75085ea2987e28e#nfc%E3%82%BF%E3%82%B0)
参考: [Raspberry PiにNFCリーダを接続してSuicaを読み取る](https://qiita.com/undo0530/items/89540a03252e2d8f291b#idm%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99)

## 環境変数から情報を取る

Python の話です。
Felica における IDm や NFC の UID 、APIのAuthTokenといった秘匿しておきたい情報についてはコード上に載せず、
環境変数から取ってくるようにしました。

`os.environ.get()` コマンドで取得出来ます

## API

必要なAPIコールはそれぞれ関数として定義しておきます

### 操作

[APIについて](https://qiita.com/mizutoki79/items/63413a977a9f64344587#%E3%82%BB%E3%82%B5%E3%83%9F%E3%81%AE%E6%93%8D%E4%BD%9C)

```python

def control_sesame(device_id, command):
    url_control = "{0}/sesame/{1}".format(api_endpoint, device_id)
    head_control = {"Authorization": auth_token,
                    "Content-Type": "application/json"}
    payload_control = {"command": command}
    response_control = requests.post(
        url_control, headers=head_control, data=json.dumps(payload_control))
    return response_control
```

### 結果確認

[APIについて](https://qiita.com/mizutoki79/items/63413a977a9f64344587#%E6%93%8D%E4%BD%9C%E7%B5%90%E6%9E%9C%E3%81%AE%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B)

```python

def check_sesame_task(task_id):
    url_check = "{0}/action-result?task_id={1}".format(api_endpoint, task_id)
    head_check = {"Authorization": auth_token}
    response_check = requests.get(url_check, headers=head_check)
    return response_check
```

### 解錠

```python

def unlock_sesame(device_id, card_id):
    response_control = control_sesame(device_id, "unlock")
    if response_control == None and not hasattr(response_control, "_content"):
        return False

    # print vars(response_control)
    response_control_content = json.loads(response_control._content)
    task_id = response_control_content["task_id"]
    print "task_id = {0}".format(task_id)

    time.sleep(7.0)
    response_check = check_sesame_task(task_id)
    if response_check == None and not hasattr(response_check, "_content"):
        return False

    response_check_content = json.loads(response_check._content)
    # print response_check_content
    if response_check_content["status"] == "processing":
        print "processing"
        pass

    if response_check_content["status"] == "terminated":
        result = response_check_content["successful"]
        print "[{0}] card_id: {1} device_id: {2} unlock: {3}".format(datetime.now().strftime("%Y/%m/%d %H:%M:%S"), card_id, device_id, "successful" if result else "failed")
        return
```

## メイン

```python
if __name__ == "__main__":
    # NFC接続リクエストのための準備
    target_req_nfc = nfc.clf.RemoteTarget("106A")
    # Suica接続リクエストのための準備
    target_req_felica = nfc.clf.RemoteTarget("212F")

    print "Waiting for NFC..."
    while True:
        # USBに接続されたNFCリーダに接続してインスタンス化
        clf = nfc.ContactlessFrontend("usb")
        # NFC待ち受け開始
        # clf.sense( [リモートターゲット], [検索回数], [検索の間隔] )
        target_res = clf.sense(target_req_nfc, target_req_felica, iterations=int(
            TIME_cycle//TIME_interval) + 1, interval=TIME_interval)

        if target_res != None:
            print target_res
            print vars(target_res)
            if not hasattr(target_res, "_brty_send"):
                continue

            brty = target_res._brty_send
            if brty == "106A":
                tag = nfc.tag.activate_tt2(clf, target_res)
            elif brty == "212F":
                tag = nfc.tag.activate_tt3(clf, target_res)
            else:
                continue

            print tag
            if tag != None:
                print vars(tag)
                print tag.type

                # Felica
                if hasattr(tag, "type") and tag.type == "Type3Tag":
                    tag.sys = 3
                    idm = binascii.hexlify(tag.idm)
                    print "Felica detected. idm = {0}".format(idm)
                    if idm in key_idms:
                        unlock_sesame(device_id, idm)

                # NFC
                else:
                    if not hasattr(tag, "_nfcid"):
                        print "Error: tag doesn't have nfcid"
                        continue

                    uid = binascii.hexlify(tag._nfcid)
                    print "NFC detected. uid = {0}".format(uid)
                    if uid in key_uids:
                        unlock_sesame(device_id, uid)

            # 共通
            print "sleep {0} seconds".format(str(TIME_wait))
            time.sleep(TIME_wait)

        clf.close()
```

```python
            if brty == "106A":
                tag = nfc.tag.activate_tt2(clf, target_res)
            elif brty == "212F":
                tag = nfc.tag.activate_tt3(clf, target_res)
```

の部分ですが、Windowsで行っていた際、 `nfc.tag.activate(clf, target_res)` でどちらも対応できましたが、
raspbian だとそれぞれTypeを指定しないと動きませんでした。

コード全体は [GitHub](https://github.com/mizutoki79/NfcSesameUnlocker) に置いてあります