ESP-WROOM-32とNFCを用いた鍵管理システム

## はじめに
　今回私が作成したのは、鍵にNFCタグを貼っておき、その鍵をNFCリーダの上に置くことで、サーバに鍵の存在を送信するシステムである。(本記事ではサーバ側は割愛する。要望があれば書くかも？)
　ことの発端は、職場の部屋に入るためには鍵を借りる必要があり、最初に来た人が借りていくのだが、誰かがいると思って部屋に向かってみると開いていなことが多々あった。鍵を借りる場所は部屋からかなり離れており、なかなか取りに行くのが面倒であるので、鍵が部屋にあることを確認できれば便利だと考えた。
　NFC以外に物体検出や重さによる識別、電気的な識別などの試行錯誤を行ったが、一番安定したNFCについてまとめる。

## 準備
本システムに用いたものは以下の通りである。(リンクは基本的にAmazon)
※必ずしも同じものでなくてもよい
- PC
- [ESP32 DevKitC(以下、ESP32)](https://www.amazon.co.jp/dp/B06XWP81GP/ref=cm_sw_r_tw_dp_HDNK61RHA83NCNQP1PF8?_encoding=UTF8&psc=1)
- [NFCリーダ(以下、リーダ)(FeloCaは非対応)](https://www.amazon.co.jp/dp/B088FLF8MC/ref=cm_sw_r_tw_dp_T7ER2MS1B0ZXANTEQ5H6?_encoding=UTF8&psc=1)
- [NFCタグ](https://www.amazon.co.jp/dp/B07P61XH3K/ref=cm_sw_r_tw_dp_DRXJP9ECX7CEWJ81F82E?_encoding=UTF8&psc=1)
- [ブレッドボード(以前、購入したものが見つからなかったので代替)](https://www.amazon.co.jp/dp/B08KCQHQGQ/ref=cm_sw_r_tw_dp_A40HNC24TMJ7RX635ADE)
- [ジャンパーワイヤー](https://www.amazon.co.jp/dp/B07LG6R78R/ref=cm_sw_r_tw_dp_WDY8XSMDFGQYPPVWZP6X?_encoding=UTF8&psc=1)
- LED　2色
- LED用の抵抗
    - [抵抗値の計算(秋月電子通商)](https://akizukidenshi.com/catalog/contents2/led-r-calc-pc.aspx)
- プッシュスイッチ
- PCとESP32を接続する用のケーブル

また、ソフトウェアとして以下を用いた。
- [Arduino IDE](https://www.arduino.cc/en/software)
    - [インストール方法(ザックリ解説Web)](https://zakkuri-kaisetsu.com/arduino-ide/#toc7)

## 配線
### 全体像
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/27b6e7cb-d02f-d171-e6a6-6a9e5312daa3.jpeg" width=50%>

※ESP32に給電することで動作します。いつもは古いスマホの充電器で動かしてます。
※ESP32が大きいので2つのブレッドボードを分解してつなげなおしています。
※プッシュスイッチは長時間の使用などで動作が安定しなくなった場合のリセットボタンです。(ESP32上のボタンが押しにくい、、、)
### 配線
| 経路1 | 経路2 | 経路3 | 経路4 |
| ----- | ----- | ----- | ----- |
| ESP32 5番ピン | リーダ SDA |
| ESP32 18番ピン | リーダ SCK | 
| ESP32 23番ピン | リーダ MOSI | 
| ESP32 19番ピン | リーダ MISO | 
| ESP32 GNDピン | リーダ GND | 
| ESP32 26番ピン | リーダ RST | 
| ESP32 3V3ピン | リーダ 3.3V | 
| ESP32 4番ピン | 抵抗 | (+)LED1(-) | ESP32 GNDピン |
| ESP32 16番ピン | 抵抗 | (+)LED2(-) | ESP32 GNDピン |
| ESP32 ENピン | プッシュスイッチ | ESP32 GNDピン |

※一対一で配線するとESP32のGNDピンが足りなくなると思うので、ブレッドボードをうまく使ってください。
※抵抗のところはLEDにあったものを使って下さい。

## プログラム
### Arduino IDEの準備
- ファイル＞環境設定＞追加のボードマネージャーのURLに
`https://dl.espressif.com/dl/package_esp32_index.json`
を追加
- ツール＞ボード＞ESP32 Arduino＞ESP32 Dev Moduleを選択
- 足りていないライブラリをインストール

### コード
https://github.com/datesann0109/key-management
```
#include <SPI.h>
#include <MFRC522.h>
#include <WiFiClientSecure.h>

#define UID "(変更)00 00 00 00 00 00 00" // NFCタグのUID　スマホアプリなどで確認できる
#define SLEEP 2 // 鍵をリーダから取ってから何秒後にサーバに送信するか(2以上)
#define STOPTIME 30 // 鍵の変化がなかった場合、サーバへの送信を何回分スキップするか

const char* ssid     = "(変更)SSID";
const char* password = "(変更)パスワード";

#define RST_PIN 26
#define SS_PIN 5

#define OPEN 1
#define CLOSE 0

const char* host = "(変更)送信先のサーバ";

//HTTPSを用いる場合の認証(証明書は定期的に更新)
//詳しくは　https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html
const char* root_ca = \
    "-----BEGIN CERTIFICATE-----\n"\
    (略)
    "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"\
    (略)
    "-----END CERTIFICATE-----\n";


int led_blue = 16;
int led_red = 4;

int timer = SLEEP;
int counter = 0;
int flag = CLOSE;

MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

WiFiClientSecure client;

void setup() {
    Serial.begin(115200);
    pinMode(led_blue, OUTPUT);
    pinMode(led_red, OUTPUT);
    digitalWrite(led_red, HIGH); // 赤いLEDを光らせる
    while (!Serial);
    
    WiFi.begin(ssid, password);
    Serial.print("try");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    
    SPI.begin();
    mfrc522.PCD_Init();
    mfrc522.PCD_DumpVersionToSerial();

    client.setCACert(root_ca);
}

void loop() {
    delay(1000); // 1秒待つ
    
    if ( timer >= SLEEP ){
        // 部屋をクローズ扱いに
        timer = SLEEP;
        digitalWrite(led_blue, LOW); // 青いLEDを消す
        digitalWrite(led_red, HIGH); // 赤いLEDを光らせる
        if ( flag == OPEN){
            // flagの入れ替え
            counter = STOPTIME;
            flag = CLOSE;
        }
    }else{
        digitalWrite(led_red, LOW); // 赤いLEDを消す
        digitalWrite(led_blue, HIGH); // 青いLEDを光らせる
        if ( flag == CLOSE){
            counter = STOPTIME;
            flag = OPEN;
        }
    }

    counter++; //STOPTIME秒に一回サーバーへ送信
    if ( counter > STOPTIME){
        if (!client.connect(host, 443)) {
            Serial.println("connection failed");
            return;
        }

        // 今回は/api/room/state/openで鍵があることをサーバに通知し、/api/room/state/closeでないことを通知する
        String url = "/api/room/state/";
        if ( flag == OPEN){
            url += "open";
        }else{
            url += "close";
        }

        client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                     "Host: " + host + "\r\n" +
                     "accept: application/json\r\n" +
                     "Authorization: (変更:サーバ側に認証がない場合いらない行)" +
                     "Connection: close\r\n\r\n");
    
        // 応答がない場合はあきらめる
        unsigned long timeout = millis();
        while (client.available() == 0) {
            if (millis() - timeout > 1000) {
                Serial.println(">>> Client Timeout !");
                client.stop();
                return;
            }
        }
    
        // 結果をシリアルポートに通知
        while(client.available()) {
            String line = client.readStringUntil('\r');
            Serial.println(line);
        }
        counter = 0;
    }
    
    if ( ! mfrc522.PICC_ReadCardSerial()) {//カードが読み取れなかった場合に実行
        Serial.println(timer);
        timer++;
        return;
    }

    // 読み込んだNFCのUIDを取得
    String strBuf[mfrc522.uid.size];
    for (byte i = 0; i < mfrc522.uid.size; i++) {
        strBuf[i] =  String(mfrc522.uid.uidByte[i], HEX);
        if(strBuf[i].length() == 1){
          strBuf[i] = "0" + strBuf[i];
        }
    }

    String strUID = strBuf[0] + " " + strBuf[1] + " " + strBuf[2] + " " + strBuf[3] + " " + strBuf[4] + " " + strBuf[5] + " " + strBuf[6];
    // 登録してあるUIDと等しければタイマーをリセット、そうでなければerror
    if ( strUID.equalsIgnoreCase(UID) ){
        //Serial.println("access");
        timer = 0;
    } else {
        //Serial.println("error!");
        digitalWrite(led_blue, LOW); // 青いLEDを消す
        digitalWrite(led_red, HIGH); // 赤いLEDを光らせる
    }
    
}
```
## 実行
### 認識していない場合
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/04f7965d-7590-665e-834e-114a2f15ee4f.jpeg" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/e465abd4-708e-06e2-1e1e-ee8e663cffd0.png" width=50%>

### 認識している場合(鍵の代わりに紙に貼ったNFCタグ)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/d11596d5-ae69-754d-2af5-2cd81ec44d7f.jpeg" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/f8c01ae2-4ce9-f016-0e20-35e82799e774.png" width=50%>

## まとめ
　目的通りにNFCタグを認識してサーバに通知することができた。昨年作ったものを思い出しながら書いたため、抜けてる点があるかもしれないが、ご容赦いただきたい。今後の展望として、3Dプリンターで専用のケースの作成やプリント基板への置き換えを計画しているが、それはまた今度で。以下、専用ケースのチラ見せ↓
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/eda7b0f5-cec6-6f4d-e90d-86958e704e95.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1277753/83644bad-2231-b3b8-17a9-aa8f500a6729.png" width=50%>