Как стать автором
Обновить
2762.57
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Сейф с доступом по отпечатку пальца

Время на прочтение6 мин
Количество просмотров7K
Автор оригинала: adimiller
Этот проект основан на предыдущем, в котором я заменил плату управления старого сейфа на ESP8266 D1 Mini, превратив его в современный сейф с одноразовым паролем. На этот раз я добавлю альтернативный способ открывания с помощью отпечатка пальца, включая возможность регистрации новых отпечатков.

Задача — заставить работать сканер параллельно с панелью ввода, чтобы можно было открывать сейф как с помощью одноразового пароля, так и с помощью отпечатка.

Интересно становится, когда задумываешься о том, как регистрировать новые отпечатки. Регистрация означает «обучение» сейфа новому отпечатку, авторизованному для отпирания сейфа. Дактилоскопический датчик может хранить до 127 сигнатур, сопоставляя отпечаток прикладываемого пальца со всеми зарегистрированными шаблонами. Для входа в режим регистрации необходимо отпереть сейф (с помощью ранее установленного отпечатка или одноразового пароля).

После отпирания у вас есть 10 секунд, в течение которых светодиод сканера мигает фиолетовым. Если в течение этого времени ввести с помощью кнопочной панели число в диапазоне 1 — 127, сопроводив его А или B, то микроконтроллер войдёт в режим регистрации и сохранит отпечаток в ячейке 1 — 127 согласно вводу. Для регистрации пользователю нужно дважды приложить палец к сенсору.

Оборудование




В качестве сканера я задействовал ёмкостный дактилоскопический датчик R503.

Я планировал установить его вместо цилиндра замка сейфа, но обнаружил, что у сканера диаметр внутренней резьбы равен 23.5мм, что на 5.5мм больше, чем у оригинального цилиндра. И всё сканер я заказал, предположив, что найду способ его туда втиснуть. К счастью, выяснилось, что благодаря внешнему диаметру (25мм) он плотно прилегает к пластиковой накладке, не требуя проталкивания вглубь дверцы.

Шаг 1: замена цилиндра ключа сканером



Дополнительные фото










Чтобы разобрать цилиндр, нужно просто открутить его заднюю часть (с внутренней стороны дверцы). Таким образом, снимается механизм, позволяющий ключу открывать замок. Затем нужно свинтить ещё одну шайбу, после чего цилиндр можно снять. Для полноценного доступа мне также потребовалось снять лицевую панель.

Сканер не встаёт в точности в паз из-под цилиндра (23.5мм против 18мм), но места в панели хватило, поскольку он должен выпирать дальше, чем замок. После установки провода удалось аккуратно просунуть через оригинальное отверстие под цилиндр.

Шаг 2: подключение



Дополнительные фото




Подключается R503 довольно просто — для этого лишь нужно подсоединить провода передачи данных (TX/RX) к любому выводу данных на микроконтроллере (не путать с выводами микроконтроллера TX/RX). Помимо этого, ему требуется питание 3В и GND.

Тем не менее в контексте моего предыдущего проекта здесь возникли сложности, поскольку доступных выводов не было, а использовать D8 и D0 было нельзя, так как это влияло на загрузку устройства. В результате мне пришлось перестроить соединение зуммера и реле, после чего для подключения TX и RX удалось задействовать выводы D6 и D7 соответственно.

Arduino  |  Component  
---------+-------------
D0       |  Keypad-Col1     
D5       |  Buzzer-Red
D6       |  R503-TX
D7       |  R503-RX
D8       |  Keypad-Col2
TX       |  Keypad-Row1
RX       |  Keypad-Row2
D1       |  Relay Signal
D2       |  Keypad-Row3
D3       |  Keypad-Row4
D4       |  Keypad-Col3
3V       |  R503-VCC
GND      |  R503-GND, Relay-GND, Buzzer-Black
5V       |  Relay-VCC

Шаг 3: проверка отпечатка


Проверка отпечатка — это относительно простая процедура. В библиотеке Adafruit_Fingerprint для этого есть рабочий пример. Ниже показана упрощённая версия кода, ссылка на полную дана в конце статьи.

#include <Adafruit_Fingerprint.h>

#define buzzer D5

// ## Считыватель отпечатка
SoftwareSerial mySerial(D6, D7);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
unsigned long lastTry = 0;

void setup() {
  pinMode(buzzer, OUTPUT);

  finger.begin(57600);

  if (!finger.verifyPassword()) {
    Serial.println("Fingerprint reader not found!");
    for (int i = 0; i < 3; i++) {
      tone(buzzer, 4500, 500);
      delay(1000);
    }
  }

  finger.getTemplateCount();
  if (finger.templateCount == 0) {
    Serial.println("Fingerprint reader doesn't have any signatures!");
    for (int i = 0; i < 2; i++) {
      tone(buzzer, 4500, 500);
      delay(1000);
    }
  }
}

void loop() {
  // Проверка отпечатка
  if (millis() >= lastTry) {
    finger.LEDcontrol(FINGERPRINT_LED_OFF, 0, FINGERPRINT_LED_BLUE);
    int fingerId = getFingerprintID();
    if (fingerId > 0) {
      openSafe();
    } else if (fingerId == -1) {
      wrongPin();
    }
  }
}

void openSafe() {
  finger.LEDcontrol(FINGERPRINT_LED_GRADUAL_ON, 200, FINGERPRINT_LED_BLUE);
  digitalWrite(relayLockPin, LOW);
  delay(100);
  tone(buzzer, 3000, 500;
  lastTry = millis() + 10000;
}

void wrongPin() {
  finger.LEDcontrol(FINGERPRINT_LED_FLASHING, 25, FINGERPRINT_LED_RED, 3);
  delay(100);
  for (int i = 0; i < 2; i++) {
    tone(buzzer, 4500, 100/beepFactor);
    delay(200);
  }
  tone(buzzer, 4500, 300/beepFactor);
  delay(400);
}

int getFingerprintID() {
  uint8_t p = finger.getImage();
  if (p == FINGERPRINT_NOFINGER || finger.image2Tz() != FINGERPRINT_OK) {
    return 0;
  }

  p = finger.fingerSearch();
  if (p == FINGERPRINT_OK) {
    return finger.fingerID;
  } else {
    lastTry = millis() + 1000;
  }

  return -1;
}

Мы указываем контакты ввода-вывода, используемые для TX/RX, и инициализируем сканер отпечатка.

SoftwareSerial mySerial(D6, D7);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);

Во время setup() сначала проверяется доступность сканера. Если он недоступен, то именно здесь нужно либо произвести выход, либо просто установить флаг, чтобы отключить остальную функциональность.

Кроме того, можно проверить количество хранящихся в сканере сигнатур и дать звуковую обратную связь в случае, если их обнаружить не удастся.

void setup() {
  finger.begin(57600);

  if (!finger.verifyPassword()) {
    Serial.println("Fingerprint reader not found!");
  }

  finger.getTemplateCount();
  if (finger.templateCount == 0) {
    Serial.println("Fingerprint reader doesn't have any signatures!");
    for (int i = 0; i < 2; i++) {
      tone(buzzer, 4500, 500);
      delay(1000);
    }
  }
}

Внутри loop() происходит непрерывное сканирование отпечатка. Если к сканеру не приложен палец, или сканирование проваливается, то getFingerprintID() возвращает 0. Если же палец к сканеру приложен, функция либо вернёт ID (1-127) совпавшего отпечатка, либо -1 при отсутствии совпадений.

void loop() {
  int fingerId = getFingerprintID();
  if (fingerId > 0) {
    openSafe();
  } else if (fingerId == -1) {
    wrongPin();
  }
}

Шаг 4: регистрация нового отпечатка


Это часть кода уже посложнее. Ради краткости я удалил отсюда некоторые фрагменты. Компилируемую версию можно найти в репозитории проекта.

void loop() {
  // Регистрация отпечатка
  char customKey = customKeypad.getKey();
  if (customKey) {
    if (customKey != 'A' && customKey != 'B' && codeIndex <= 3) {
      code[codeIndex] = customKey;
      codeIndex = codeIndex + 1;
      Serial.print("Fingerprint Id: ");
      Serial.println(code);
    } else {
      uint16_t slotId = atoi(code);
      codeIndex = 0;
      memset(code, 0, 8);
      if (slotId >= 1 && slotId <= 127) {
        Serial.print("Fingerprint Id to enrol: "); Serial.println(slotId);
        if (getFingerprintEnroll(slotId)) {
          // Успех
        } else {
          // Ошибка
        }
      }
    }
  }
}

bool getFingerprintEnroll(uint8_t id) {
  if (sampleFinger(1)) {
    Serial.print("Remove finger.");
    while (finger.getImage() != FINGERPRINT_NOFINGER) {
      Serial.print(".");
      delay(10);
    }
    Serial.println();
    delay(500);

    if (sampleFinger(2)) {
      Serial.println("Creating model");
      int p = finger.createModel();
      if (p == FINGERPRINT_OK) {
        Serial.println("Prints matched!");
        p = finger.storeModel(id);
        if (p == FINGERPRINT_OK) {
          Serial.println("Stored!");
          return true;
        } else {
          Serial.println("Error storing fingerprint model");
        }    
      } else {
        Serial.println("Fingerprints did not match or other error");
      }
    }
  }
  return false;
}

bool sampleFinger(uint8_t slot) {
  int p = -1;
  while (p != FINGERPRINT_OK && p != FINGERPRINT_PACKETRECIEVEERR) {
    p = finger.getImage();
    switch (p) {
      case FINGERPRINT_OK:
        Serial.println("Image taken");
        break;
      case FINGERPRINT_NOFINGER:
        Serial.print(".");
        break;
      case FINGERPRINT_PACKETRECIEVEERR:
        Serial.println("Communication error");
        break;
      case FINGERPRINT_IMAGEFAIL:
        Serial.println("Imaging error");
        return false;
      default:
        Serial.println("Unknown error");
        return false;
    }
  }

  p = finger.image2Tz(slot);
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println("Image converted");
      break;
    case FINGERPRINT_IMAGEMESS:
      Serial.println("Image too messy");
      break;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println("Communication error");
      break;
    case FINGERPRINT_FEATUREFAIL:
      Serial.println("Could not find fingerprint features");
      break;
    case FINGERPRINT_INVALIDIMAGE:
      Serial.println("Could not find fingerprint features");
      break;
    default:
      Serial.println("Unknown error");
      break;
  }
  return (p == FINGERPRINT_OK);
}

Внутри loop() мы проверяем, является ли набранное число (предшествующее вводу А или В) допустимым номером ячейки для отпечатка (1-127). Если да, то вызываем функцию getFingerprintEnroll() с переданным значением ячейки.

Процесс регистрации получает первый образец отпечатка, после чего ожидает от сканера сообщения FINGERPRINT_NOFINGER, указывающего, что палец был убран. После этого считывается второй образец. Далее оба отпечатка проходят тесты, подтверждающие их идентичность.

int p = finger.createModel();
if (p == FINGERPRINT_OK) {
  Serial.println("Prints matched!");
}

В завершении, если всё прошло успешно, созданный паттерн сохраняется.
p = finger.storeModel(id);
if (p == FINGERPRINT_OK) {
  Serial.println("Stored!");
}

Шаг 5: итоги


Полная версия кода доступна на GitHub. Она включает поддержку одноразового пароля, работу с замком и прочее.

Я не тестировал его на других сканерах отпечатков, но в теории всё должно работать. Будет здорово, если в комментариях вы поделитесь собственным опытом тюнинга сейфов.

Повторю дисклеймер из оригинального проекта «Time-Based One-Time Password (TOTP) Smart Safe — Instructables»:

Очевидно, что этот сейф не такой уж “safe”. Помимо риска взлома самого сейфа, здесь присутствуют и другие потенциальные точки сбоя. Если Di Mini умрёт, то вы вообще не сможете с ним взаимодействовать. Так что будьте осторожны.

Теги:
Хабы:
Всего голосов 39: ↑37 и ↓2+35
Комментарии10

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds