Задача — заставить работать сканер параллельно с панелью ввода, чтобы можно было открывать сейф как с помощью одноразового пароля, так и с помощью отпечатка.
Интересно становится, когда задумываешься о том, как регистрировать новые отпечатки. Регистрация означает «обучение» сейфа новому отпечатку, авторизованному для отпирания сейфа. Дактилоскопический датчик может хранить до 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 умрёт, то вы вообще не сможете с ним взаимодействовать. Так что будьте осторожны.
Комментарии (8)
Vindicar
27.03.2022 21:02+5У Lock Picking Lawyer на ютубе есть целая подборка про сейфы с отпечатком пальца. Большинство открываются через паршивенький «резервный» механический замок. X)
shaposhnikow
28.03.2022 05:12+2С этим "сейфом" (по факту стальной коробочкой) есть один нюанс:
он открывается голыми руками, правда нужно немного потренироваться.
Суть способа: синхронно наносится удар снизу и поворот рукоятки. В момент удара подпружиненный сердечник соленоида кратковременно разблокирует механизм.
CustomRC
28.03.2022 19:51+3Все пишут что этот сейф можно открыть голыми руками, я вот правда не особо понимаю в чем проблема просто перенести уже готовую технологию, представленную человеком на другой сейф, возможно с другим замком, вам же не говорят что это единственное представление сейфа, это по сути заготовка для тех, кто хочет по приколу или нет, собрать сейф.
beerware
Высверливаем сканер, получаем доступ к электронике...
Kotofay
Отламываем панель и через щель плоского шлейфа отодвигаем якорь соленоида.
kurvimetr
Варвары =))))
navferty
Нет, варвары - это "отламываем палец и получаем доступ к содержимому без участия носителя пальца" =)
0xdead926e
да зачем что-то ломать, если там механический замок для оверрайда настолько плох, что негоден даже для почтовых ящиков?