因为Linux系统每次开机或者执行一些需要权限的命令时要输入密码,但是本身输入密码这件事就不太安全。综合现有情况,windows可以很方便的使用人脸识别,但是linux上指纹都不方便,也怕破坏启动配置。于是决定使用外部硬件的方式实现指纹识别后自动输入密码的功能。相对安全了点。
功能逻辑¶
- 通过蓝牙连接ESP32
- 通过AS608校验指纹
- 指纹校验通过则模拟成蓝牙键盘发送密码
硬件方面¶
- 采用的ESP32作为主控,其本身自带WIFI和蓝牙,非常便于之后的功能改善。
- AS608作为指纹传感器,负责指纹的录入和校验。
- OLED SSD1306作为屏幕进行必要的显示,方便了解处理进度。
ESP32的管脚图:
ESP32原理图:
IDE图方便选择的是Arduino的环境,在板子管理处添加如下字段,添加Esp32板子信息:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
。
选择开发版ESP32 Dev Module
。
安装库:ESP8266 and ESP32 OLED driver for SSD1306 displays
和Adafruit Fingerprint Senser Library
。
安装第三方库:从https://github.com/T-vK/ESP32-BLE-Keyboard
下载并添加。
电路连接¶
OLED:
- VCC->3.3V
- GND->GND
- SCL->接D22
- SDA->D21
AS608:
- VCC->3.3V(5V会烧坏元件)
- GND->GND
- TX->RX2
- RX->TX2
代码¶
指纹注册代码¶
#include <Adafruit_Fingerprint.h>
#define mySerial Serial2
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
uint8_t id;
void setup()
{
Serial.begin(9600);
while (!Serial);
delay(100);
Serial.println("\n\nAdafruit Fingerprint sensor enrollment");
// 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); }
}
Serial.println(F("Reading sensor parameters"));
finger.getParameters();
Serial.print(F("Status: 0x")); Serial.println(finger.status_reg, HEX);
Serial.print(F("Sys ID: 0x")); Serial.println(finger.system_id, HEX);
Serial.print(F("Capacity: ")); Serial.println(finger.capacity);
Serial.print(F("Security level: ")); Serial.println(finger.security_level);
Serial.print(F("Device address: ")); Serial.println(finger.device_addr, HEX);
Serial.print(F("Packet len: ")); Serial.println(finger.packet_len);
Serial.print(F("Baud rate: ")); Serial.println(finger.baud_rate);
}
uint8_t readnumber(void) {
uint8_t num = 0;
while (num == 0) {
while (! Serial.available());
num = Serial.parseInt();
}
return num;
}
void loop() // run over and over again
{
Serial.println("Ready to enroll a fingerprint!");
Serial.println("Please type in the ID # (from 1 to 127) you want to save this finger as...");
id = readnumber();
if (id == 0) {// ID #0 not allowed, try again!
return;
}
Serial.print("Enrolling ID #");
Serial.println(id);
while (! getFingerprintEnroll() );
}
uint8_t getFingerprintEnroll() {
int p = -1;
Serial.print("Waiting for valid finger to enroll as #"); Serial.println(id);
while (p != FINGERPRINT_OK) {
p = finger.getImage();
switch (p) {
case FINGERPRINT_OK:
Serial.println("Image taken");
break;
case FINGERPRINT_NOFINGER:
Serial.println(".");
break;
case FINGERPRINT_PACKETRECIEVEERR:
Serial.println("Communication error");
break;
case FINGERPRINT_IMAGEFAIL:
Serial.println("Imaging error");
break;
default:
Serial.println("Unknown error");
break;
}
}
// OK success!
p = finger.image2Tz(1);
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;
}
Serial.println("Remove finger");
delay(2000);
p = 0;
while (p != FINGERPRINT_NOFINGER) {
p = finger.getImage();
}
Serial.print("ID "); Serial.println(id);
p = -1;
Serial.println("Place same finger again");
while (p != FINGERPRINT_OK) {
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");
break;
default:
Serial.println("Unknown error");
break;
}
}
// OK success!
p = finger.image2Tz(2);
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!
Serial.print("Creating model for #"); Serial.println(id);
p = finger.createModel();
if (p == FINGERPRINT_OK) {
Serial.println("Prints matched!");
} else if (p == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return p;
} else if (p == FINGERPRINT_ENROLLMISMATCH) {
Serial.println("Fingerprints did not match");
return p;
} else {
Serial.println("Unknown error");
return p;
}
Serial.print("ID "); Serial.println(id);
p = finger.storeModel(id);
if (p == FINGERPRINT_OK) {
Serial.println("Stored!");
} else if (p == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return p;
} else if (p == FINGERPRINT_BADLOCATION) {
Serial.println("Could not store in that location");
return p;
} else if (p == FINGERPRINT_FLASHERR) {
Serial.println("Error writing to flash");
return p;
} else {
Serial.println("Unknown error");
return p;
}
return true;
}
删除指纹代码¶
#define mySerial Serial2
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
void setup()
{
Serial.begin(9600);
while (!Serial); // For Yun/Leo/Micro/Zero/...
delay(100);
Serial.println("\n\nDelete Finger");
// 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);
}
}
uint8_t readnumber(void) {
uint8_t num = 0;
while (num == 0) {
while (! Serial.available());
num = Serial.parseInt();
}
return num;
}
void loop() // run over and over again
{
Serial.println("Please type in the ID # (from 1 to 127) you want to delete...");
uint8_t id = readnumber();
if (id == 0) {// ID #0 not allowed, try again!
return;
}
Serial.print("Deleting ID #");
Serial.println(id);
deleteFingerprint(id);
}
uint8_t deleteFingerprint(uint8_t id) {
uint8_t p = -1;
p = finger.deleteModel(id);
if (p == FINGERPRINT_OK) {
Serial.println("Deleted!");
} else if (p == FINGERPRINT_PACKETRECIEVEERR) {
Serial.println("Communication error");
return p;
} else if (p == FINGERPRINT_BADLOCATION) {
Serial.println("Could not delete in that location");
return p;
} else if (p == FINGERPRINT_FLASHERR) {
Serial.println("Error writing to flash");
return p;
} else {
Serial.print("Unknown error: 0x"); Serial.println(p, HEX);
return p;
}
}
使用代码¶
#include "SSD1306Wire.h" // legacy: #include "SSD1306.h"
#include <Wire.h>
#include <BleKeyboard.h>
#include <Adafruit_Fingerprint.h>
#define mySerial Serial2
BleKeyboard bleKeyboard;
SSD1306Wire display(0x3c, 21, 22);
Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);
//ESP32上的蓝色指示灯,GPIO2控制
const int led=2;
void displayHello(){
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_16);
display.drawStringMaxWidth(0, 0, 128,
"Weclome to use King's fingerprint bluetooth keyboard!" );
display.display();
}
void displayMeg(String meg,int showTime=200){
display.clear();
display.drawStringMaxWidth(0,0,128,meg);
display.display();
delay(showTime);
}
void displayMeg3line(String meg1,String meg2,String meg3,int showTime=1000){
display.clear();
display.drawString(0,0,meg1);
display.drawString(0,16,meg2);
display.drawString(0,32,meg3);
display.display();
delay(showTime);
}
void connectFinger(){
if (finger.verifyPassword()) {
displayMeg("Found fingerprint sensor! :)");
} else {
display.clear();
//Serial.println("Did't find fingerprint sensor :(");
display.drawStringMaxWidth(0,0,128,"Did't find fingerprint sensor :(");
display.display();
delay(2000);
while (1) {
delay(2000);
display.clear();
//Serial.println("waiting for fingerprint sensor...");
display.drawStringMaxWidth(0,0,128,"Waiting for fingerprint sensor...");
display.display();
}
}
}
void getValid(){
displayMeg("Waiting for valid finger...",1000);
finger.getTemplateCount();
if (finger.templateCount == 0) {
displayMeg("Sensor doesn't contain any fingerprint data. Please run the 'enroll'.");
}
else {
displayMeg("Sensor contains templates! ._.");
}
}
void sendPassword(uint8_t id){
digitalWrite(led,HIGH);
display.clear();
char stringId[25];
itoa(id,stringId,10);
display.drawString(0,0,"Fingerprint id :");
display.drawString(0,16,stringId);
display.display();
if(bleKeyboard.isConnected()) {
bleKeyboard.print("hello world");//自定义密码
bleKeyboard.write(KEY_RETURN);
}
display.drawString(0,32,"Key sended by BT!");
display.display();
delay(3000);
digitalWrite(led,LOW);
}
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.fingerSearch();
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);
sendPassword(finger.fingerID);
return finger.fingerID;
}
void checkBlueTooth(){
String bluetoothStatue;
if(bleKeyboard.isConnected()){
bluetoothStatue="BT connected!";
displayMeg3line(bluetoothStatue,"Waiting your fing-","er,King!",50);
}else{
bluetoothStatue="BT unconnected!";
displayMeg3line(bluetoothStatue,"Please restart or ","connect!",50);
}
}
void setup() {
//Serial.begin(115200);
//Serial.println("Weclome to use King's fingerprint bluetooth keyboard!");
pinMode(2, OUTPUT);
display.init();
display.flipScreenVertically();
displayHello();
bleKeyboard.begin();
finger.begin(57600);
connectFinger();
getValid();
}
void loop() {
// displayMeg("Waiting your finger,king!");
checkBlueTooth();
getFingerprintID();
}
想要修改默认的蓝牙名称需要在默认C:\Users\<用户名>\Documents\Arduino\libraries\ESP32-BLE-Keyboard
的BleKeyboard.h
的第101行进行修改。
遇到的问题¶
在开发过程中,当OLED显示屏没有在和ESP32同时上电,可能导致OLED屏幕无法显示。但是在电脑端未出现此问题。同时采用ESP32的电源进行供电就没有此问题。
可选升级¶
通过树莓派控制供电¶
USB 接口的供电并不是通过 GPIO 直接控制的,而是通过 USB 集线器 LAN9514 控制的。CPU 可以通过设置集线器的 PORT_POWER 选项完成 USB 供电电路的开关。
控制 USB 供电的电路出现在 Raspberry Pi Model B+ 之后的版本。原版的 Raspberry Pi Model B 的 USB 供电电路直接与 5V 相连,不受控制。
-P 参数用于指定要控制的端口,1 为集成网卡,2 为 4 个 USB 口。网卡的供电可以独立控制,所以开关 USB 口的供电并不会导致 ssh 断开。
下载并编译 hub-ctrl.c(这个程序依赖于 libusb,编译时请带上参数 -lusb)。
调用如下命令控制USB的供电:
//查看设备
lsusb
//USB供电
sudo /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 1
//USB不供电
sudo /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 0
设置定时任务:
sudo crontab -e
//命令如下
//每天7点30供电,21点40停止供电
30 7 * * * /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 0
40 21 * * * /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 1
添加触摸传感器¶
为了避免长时间供电损坏引脚以及避免烧屏,采用传感器触发开启指纹。
添加触摸传感器,其只有三个引脚VCC、GND、Sig。
通过Respeaker上的Grove接口预留的GPIO12采集其信号。当检测到触摸传感器被触摸后返回的高电平信号,树莓派给USB口供电,启动指纹蓝牙键盘。使用树莓派的连接SSD1306的OLED屏幕显示树莓派当前状态,并将每次的log信息保存在项目文件夹中。
代码如下:
import time
import subprocess
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import RPi.GPIO as GPIO
import os
Touch = 12
GPIO.setmode(GPIO.BCM)
GPIO.setup(Touch, GPIO.IN)
# Create the I2C interface.
i2c = busio.I2C(SCL, SDA)
disp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)
disp.fill(0)
disp.show()
width = disp.width
height = disp.height
image = Image.new("1", (width, height))
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, width, height), outline=0, fill=0)
padding = -2
top = padding
bottom = height - padding
# Move left to right keeping track of the current x position for drawing shapes.
x = 0
font = ImageFont.truetype(
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 13)
def displayInfo():
draw.rectangle((0, 0, width, height), outline=0, fill=0)
cmd = "hostname -I | cut -d' ' -f1"
IP = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "free -m | awk 'NR==2{printf \"Mem:%s/%s %.2f%%\", $3,$2,$3*100/$2 }'"
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = 'df -h | awk \'$NF=="/"{printf "Disk:%d/%d GB %s", $3,$2,$5}\''
Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
# Write four lines of text.
draw.text((x, top + 0), "IP: " + IP, font=font, fill=255)
draw.text((x, top + 15), CPU, font=font, fill=255)
draw.text((x, top + 31), MemUsage, font=font, fill=255)
draw.text((x, top + 47), Disk, font=font, fill=255)
# Display image.
disp.image(image)
disp.show()
time.sleep(0.1)
def logToFile(filePath, *words) -> bool:
with open(filePath, 'a+', encoding="utf8")as f:
for word in words:
print(word, file=f, end="")
print(file=f)
return True
return False
def checkTouch():
info = GPIO.input(Touch)
# print(info)
if info == 1:
logToFile(filePath, time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime()))
logToFile(filePath, "*"*30)
logToFile(filePath, "FingerPrint Start!")
os.system("sudo /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 1")
draw.rectangle((0, 0, width, height), outline=0, fill=0)
draw.text((x+10, top + 16), "FingerPrint Start!", font=font, fill=255)
disp.image(image)
disp.show()
time.sleep(60)
logToFile(filePath, "FingerPrint Stop!\n"+"*"*30+"\n")
os.system("sudo /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 0")
pwd = os.getcwd()
filePath = os.path.join(pwd, "FingerLog.txt")
while True:
displayInfo()
checkTouch()
最终的crontab:
# sudo crontab -e 定时任务
40 21 * * * /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 0
# vim /etc/rc.local 开机自启
sudo /home/pi/hub-ctrl.c/hub-ctrl -b 001 -d 002 -P 2 -p 0
cd /home/pi && python3 finger.py