前回、タクトスイッチを使って外から解錠するやつを作りました。今回はいよいよスマートロックっぽいことをしていきます。なんと指紋認証に挑戦です。意外と簡単に、かつ安くできましたのでご紹介します。
作りたいもの
前回 のつづきです。ICカードで解錠はいったん置いておきます。(作ったけど、記事にするのが面倒くさい。。。)
今回の目標は、指でセンサにふれると解錠してくれるスマートロックを作ることです。
- ドアが閉まったら自動で施錠
- 暗証番号で解錠
ICカードで解錠- 指紋認証で解錠
- スマホを持って近づくだけで解錠
前回から追加で必要なもの
- 指紋モジュールDY50 1200円くらい
Amazonで最安だった指紋認識モジュールです。高いやつだと4000円くらいするのにこちらは1200円なのでちょっと不安ですが、きちんと認識してくれます。若干認識までにかかる時間が長いかも?。指を置いてから0.5~1秒くらいかかります。
導線にピンヘッダをはんだ付けする
指紋モジュールから出ている線はVCC、GND、TX、RXの四本です。これをArduinoで接続するためにピンをはんだ付けします。
(以下、作業途中の写真を撮るのを忘れたのでイメージ画像です。)
モジュール付属のコネクタ・ケーブルを半分に切断して、ジャンパワイヤとピンをはんだ付けしていきます。


ちなみに、モジュールの線の配置はこのようになっています。

ESP8266に接続
UART接続でESP8266と通信しますので、以下のように接続します。マイコンとの通信はソフトウェアシリアルを使うので、TXとRXをESPのD2とD3につなぎます。(追記:僕が使っているESP8266ではソフトウェアシリアルが使えませんでした!後述します。)
DY50 | ESP8266 |
VCC | Vin |
TX | D2 |
RX | D3 |
GND | GND |
指紋認識テスト
モジュールが接続できたら指紋認証がうまくいくかテストしてみます。ArduinoIDEのライブラリ管理から検索するか、adafruitのgitからモジュール用のドライバとサンプルプログラムをダウンロードします。
ArduinoIDEからサンプルプログラムを開いてenrollを書き込んでみます。これだけで指紋の登録ができる…と色々なサイトに書いてありますが、僕の場合は上手くいきませんでした…。プログラムを走らせてシリアルモニタを開いても「sensor not found.」と表示され、そもそも何も通信できていないようです。
色々調べた結果、原因はESP8266モジュールのソフトウェアシリアルが怪しいと結論づけました。ESP8266はデフォルトの通信レートが119200bpsであるのに対し、このモジュールは9600bpsで通信するので上手くいっていないのだと思います。ネットの記事を調べてESP8266の通信レートを9600に落とす方法を試しましたが、僕の持っているモジュールでは変更しても電源を入れ直すと通信レートが戻ってしまい上手く行きませんでした。
どうするか
ESP8266でソフトウェアシリアルが使えないならどうするか、これに結構悩みました。
案① Arduino UnoとかNanoとか他のモジュールを使う。
ただし、WiFiから解錠したり他の機器との連携は無理。NanoとESPを両方使えばできるがなんか無駄な気がする。
案② ESP8266のハードウェアシリアルを使う。
ただし、シリアルモニタが使えなくなるためデバッグができなくなる。ログをWiFi経由で確認できるようにすれば一応デバッグできる。
3日くらい悩んでましたが、今後WiFiはやっぱり使いたいのと、これまで作ってた基盤がESP用になっているのでまた1から基盤にはんだ付けし直すのが面倒という理由で案②を採用することにしました。
でもこの方法だとシリアルモニタからモジュールに数値を送信することができないんですよね…。指紋の登録時には番号を送信する必要があるので、指紋登録時は①、運用時は②という面倒くさいことになっております。
指紋を登録してみる
指紋登録だけはArduino Nanoを使って行います。adafruitのサンプルプログラムからenrollを書き込んで、指紋モジュールのTXとRXをArduinoのD2とD3につなぎます。
シリアルモニタを開いて、メッセージ通りに番号を入力して指を2回置けば登録完了です。
登録した指紋は電源を切っても保持されているので、よく使う指を全部登録したら次はESPにつないで認証できるかやってみます。
指紋を認識できるかテストしてみる
ここからはモジュールをESP8266につないで作業していきます。ソフトウェアシリアルではなくハードウェアシリアルを使うので接続はモジュールのTX、RXとESPのRX、TXです。また、プログラムの書き込みを行う際はモジュールを外さないと書き込めません。それとシリアルモニタは使えません。
書き込むプログラムはサンプルプログラムfingerprintをちょっと変えるだけでOKです。
// Adafruitのサンプルプログラムfingerprintをハードウェアシリアルに改変 // Serial.printlnは全部コメントアウトしています。 #include <Adafruit_Fingerprint.h> const int led_pin = 16; // LEDはD0に接続 // On Leonardo/Micro or others with hardware serial, use those! #0 is green wire, #1 is white // uncomment this line: // #define mySerial Serial1 // For UNO and others without hardware serial, we must use software serial... // pin #2 is IN from sensor (GREEN wire) // pin #3 is OUT from arduino (WHITE wire) // comment these two lines if using hardware serial //SoftwareSerial mySerial(2, 3); // ハードウェアシリアルを使うのでコメントアウト Adafruit_Fingerprint finger = Adafruit_Fingerprint(&Serial); // ハードウェアシリアル void setup() { //Serial.begin(9600); //while (!Serial); // For Yun/Leo/Micro/Zero/... //delay(100); //Serial.println("\n\nAdafruit finger detect test"); // set the data rate for the sensor serial port finger.begin(57600); if (finger.verifyPassword()) { //Serial.println("Found fingerprint sensor!"); } else { //Serial.println("Did not find fingerprint sensor :("); while (1) { delay(1); } } finger.getTemplateCount(); //Serial.print("Sensor contains "); Serial.print(finger.templateCount); Serial.println(" templates"); //Serial.println("Waiting for valid finger..."); pinMode(led_pin, OUTPUT); } void loop() // run over and over again { int num = getFingerprintIDez(); // 認証成功したらLEDを光らせる if( num > 0 ){ digitalWrite(led_pin, HIGH); delay(1000); digitalWrite(led_pin, LOW); } delay(50); //don't ned to run this at full speed. } uint8_t getFingerprintID() { uint8_t p = finger.getImage(); switch (p) { case FINGERPRINT_OK: //Serial.println("Image taken"); break; case FINGERPRINT_NOFINGER: //Serial.println("No finger detected"); return p; case FINGERPRINT_PACKETRECIEVEERR: //Serial.println("Communication error"); return p; case FINGERPRINT_IMAGEFAIL: //Serial.println("Imaging error"); return p; default: //Serial.println("Unknown error"); return p; } // OK success! p = finger.image2Tz(); switch (p) { case FINGERPRINT_OK: //Serial.println("Image converted"); break; case FINGERPRINT_IMAGEMESS: //Serial.println("Image too messy"); return p; case FINGERPRINT_PACKETRECIEVEERR: //Serial.println("Communication error"); return p; case FINGERPRINT_FEATUREFAIL: //Serial.println("Could not find fingerprint features"); return p; case FINGERPRINT_INVALIDIMAGE: //Serial.println("Could not find fingerprint features"); return p; default: //Serial.println("Unknown error"); return p; } // OK converted! p = finger.fingerFastSearch(); if (p == FINGERPRINT_OK) { //Serial.println("Found a print match!"); } else if (p == FINGERPRINT_PACKETRECIEVEERR) { //Serial.println("Communication error"); return p; } else if (p == FINGERPRINT_NOTFOUND) { //Serial.println("Did not find a match"); return p; } else { //Serial.println("Unknown error"); return p; } // found a match! //Serial.print("Found ID #"); Serial.print(finger.fingerID); //Serial.print(" with confidence of "); Serial.println(finger.confidence); return finger.fingerID; } // returns -1 if failed, otherwise returns ID # int getFingerprintIDez() { uint8_t p = finger.getImage(); if (p != FINGERPRINT_OK) return -1; p = finger.image2Tz(); if (p != FINGERPRINT_OK) return -1; p = finger.fingerFastSearch(); if (p != FINGERPRINT_OK) return -1; // found a match! //Serial.print("Found ID #"); Serial.print(finger.fingerID); //Serial.print(" with confidence of "); Serial.println(finger.confidence); return finger.fingerID; }
変更点としてはmySerialの部分をSerialに変えて、Serial.printlnをすべてコメントアウトするだけです。
シリアルモニタが使えないので、確認用のLEDをD0とGNDにつけます。認証が成功するとLEDを1秒間光らせます。
認証成功で解錠する
前回 のプログラムの解錠条件に、今回の指紋認証の結果を加えるだけです。最終的なプログラムを載せておきます。
#include <Servo.h> #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> //#include <wifi_setting.h> #include <Adafruit_Fingerprint.h> #include <MyFunctions.h> // ルータ設定 IPAddress ip(192,168,100,102); IPAddress gateway(192,168,100,1); IPAddress subnet(255,255,255,0); // ESP8266のピン番号 const int servo_pin = 2; const int door_pin = 12; const int button_pin = 14; Servo myservo; // サーボオブジェクトを生成 int door_sensor = LOW; int door_sensor_last = LOW; ESP8266WebServer server(80); //サーバーオブジェクト Adafruit_Fingerprint finger = Adafruit_Fingerprint(&Serial); // ハードウェアシリアル LogBuffer action_log(64); void door_open(){ myservo.attach(servo_pin); delay(50); myservo.write(170); delay(500); myservo.write(90); delay(500); myservo.detach(); } void door_close(){ myservo.attach(servo_pin); delay(50); myservo.write(10); delay(500); myservo.write(90); delay(500); myservo.detach(); } void handle_open(){ door_open(); server.send(200, "text/html", "opened"); } void handle_close(){ door_close(); server.send(200, "text/html", "closed"); } void handle_log(){ server.send(200, "text/html", action_log.to_HTML()); } bool secret_command(){ // HIGHがスイッチが押されていない状態 // LOWがスイッチが押されている状態 unsigned long pressed_time = millis(); unsigned long released_time = millis(); int now_state = LOW; int last_state = LOW; float button_lpf = 0.0; // センサ信号にローパスフィルタ(LPF)をかけた値 String key = "---.--..-."; //モールス符号でO・P・E・N String input = ""; while(millis() - released_time < 3000){ button_lpf = 0.9 * button_lpf + 0.1 * digitalRead(button_pin); if( button_lpf > 0.8 ){ now_state = HIGH; }else if( button_lpf < 0.2 ){ now_state = LOW; } if( last_state == HIGH && now_state == LOW ){ // ボタンが押されたとき、HIGH→LOW pressed_time = millis(); }else if( last_state == LOW && now_state == HIGH){ // ボタンが離されたとき、LOW→HIGH released_time = millis(); // ボタンを押していた時間で「.」か「-」を追加 input += (released_time - pressed_time < 300) ? "." : "-"; } last_state = now_state; delay(1); } if(key == input){ return true; }else{ return false; } } int getFingerprintIDez() { uint8_t p = finger.getImage(); if (p != FINGERPRINT_OK) return -1; p = finger.image2Tz(); if (p != FINGERPRINT_OK) return -1; p = finger.fingerFastSearch(); if (p != FINGERPRINT_OK) return -1; // found a match! //action_log.add("Found ID #"); action_log.add(finger.fingerID); //action_log.add(" with confidence of "); action_log.add(finger.confidence); return finger.fingerID; } void setup() { //固定IPで運用するときの設定 WiFi.config(ip, gateway, subnet); //WiFi.begin(WIFI_SSID, WIFI_PWD); // WiFiに接続するまで待つ action_log.add(""); while(WiFi.status() != WL_CONNECTED){ delay(1000); action_log.add("."); } action_log.add(""); action_log.add("Connected!"); action_log.add("IP Address: "); action_log.add(WiFi.localIP()); // Webサーバを設定 server.on("/open", handle_open); server.on("/close", handle_close); server.on("/log", handle_log); server.begin(); // 開閉センサの入力を内部プルアップにする pinMode(door_pin, INPUT_PULLUP); // サーボ変数をピンに割り当て myservo.attach(servo_pin); door_close(); // 指紋センサ finger.begin(57600); if (finger.verifyPassword()) { action_log.add("Found fingerprint sensor!"); } else { action_log.add("Did not find fingerprint sensor :("); while (1) { delay(1); } } finger.getTemplateCount(); action_log.add("Sensor contains "); action_log.add(finger.templateCount); action_log.add(" templates"); action_log.add("Waiting for valid finger..."); } void loop() { // サーバとして待ち受ける server.handleClient(); // 現在のドアの開閉を検知 // LOW=閉 // HIGH=開 door_sensor = digitalRead(door_pin); // 「開」→「閉」になったタイミングでサーボモータを回す if(door_sensor == LOW && door_sensor_last == HIGH){ delay(1000); door_close(); } door_sensor_last = door_sensor; // ボタンで解錠 if(digitalRead(button_pin) == LOW){ bool success = secret_command(); if(success){ door_open(); } } // 指紋で解錠 if( getFingerprintIDez() > 0 ){ door_open(); action_log.add("Door opened."); } delay(10); }
シリアルモニタが使えないのでWiFiからログを確認できる機能を追加しています。何かメッセージを出したいときはaction_logに保存しておき、http://192.168.xxx.xxx/logで表示します。action_logのクラスはMyFunction.hに別ファイルとして書いてます。
以上です。
(追記予定)