ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
fingerprint_grow.cpp
Go to the documentation of this file.
1#include "fingerprint_grow.h"
2#include "esphome/core/gpio.h"
3#include "esphome/core/log.h"
4#include <cinttypes>
5
7
8ESPHOME_LOG_TAG(TAG, "fingerprint_grow");
9
10// Based on Adafruit's library: https://github.com/adafruit/Adafruit-Fingerprint-Sensor-Library
11
13 if (this->enrollment_image_ > this->enrollment_buffers_) {
15 return;
16 }
17
18 if (this->has_sensing_pin_) {
19 // A finger touch results in a low level (digital_read() == false)
20 if (this->sensing_pin_->digital_read()) {
21 ESP_LOGV(TAG, "No touch sensing");
22 this->waiting_removal_ = false;
23 if ((this->enrollment_image_ == 0) && // Not in enrolment process
25 this->sensor_sleep_();
26 }
27 return;
28 } else if (!this->waiting_removal_) {
29 this->finger_scan_start_callback_.call();
30 }
31 }
32
33 if (this->waiting_removal_) {
34 if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) {
35 ESP_LOGD(TAG, "Finger removed");
36 this->waiting_removal_ = false;
37 }
38 return;
39 }
40
41 if (this->enrollment_image_ == 0) {
42 this->scan_and_match_();
43 return;
44 }
45
46 uint8_t result = this->scan_image_(this->enrollment_image_);
47 if (result == NO_FINGER) {
48 return;
49 }
50 this->waiting_removal_ = true;
51 if (result != OK) {
52 this->finish_enrollment(result);
53 return;
54 }
56 ++this->enrollment_image_;
57}
58
60 this->has_sensing_pin_ = (this->sensing_pin_ != nullptr);
61 this->has_power_pin_ = (this->sensor_power_pin_ != nullptr);
62
63 // Call pins setup, so we effectively apply the config generated from the yaml file.
64 if (this->has_sensing_pin_) {
65 this->sensing_pin_->setup();
66 }
67 if (this->has_power_pin_) {
68 // Starts with output low (disabling power) to avoid glitches in the sensor
69 this->sensor_power_pin_->digital_write(false);
70 this->sensor_power_pin_->setup();
71
72 // If the user didn't specify an idle period to sleep, applies the default.
73 if (this->idle_period_to_sleep_ms_ == UINT32_MAX) {
74 this->idle_period_to_sleep_ms_ = DEFAULT_IDLE_PERIOD_TO_SLEEP_MS;
75 }
76 }
77
78 // Place the sensor in a known (sleep/off) state and sync internal var state.
79 this->sensor_sleep_();
80 delay(20); // This delay guarantees the sensor will in fact be powered power.
81
82 if (this->check_password_()) {
83 if (this->new_password_ != std::numeric_limits<uint32_t>::max()) {
84 if (this->set_password_())
85 return;
86 } else {
87 if (this->get_parameters_())
88 return;
89 }
90 }
91 this->mark_failed();
92}
93
94void FingerprintGrowComponent::enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers) {
95 ESP_LOGI(TAG, "Starting enrollment in slot %d", finger_id);
96 if (this->enrolling_binary_sensor_ != nullptr) {
98 }
99 this->enrollment_slot_ = finger_id;
100 this->enrollment_buffers_ = num_buffers;
101 this->enrollment_image_ = 1;
102}
103
105 if (result == OK) {
108 } else {
109 if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
111 }
112 }
113 this->enrollment_image_ = 0;
114 this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
115 if (this->enrolling_binary_sensor_ != nullptr) {
117 }
118 ESP_LOGI(TAG, "Finished enrollment");
119}
120
122 if (this->has_sensing_pin_) {
123 ESP_LOGD(TAG, "Scan and match");
124 } else {
125 ESP_LOGV(TAG, "Scan and match");
126 }
127 if (this->scan_image_(1) == OK) {
128 this->waiting_removal_ = true;
129 this->data_ = {SEARCH, 0x01, 0x00, 0x00, (uint8_t) (this->capacity_ >> 8), (uint8_t) (this->capacity_ & 0xFF)};
130 switch (this->send_command_()) {
131 case OK: {
132 ESP_LOGD(TAG, "Fingerprint matched");
133 uint16_t finger_id = ((uint16_t) this->data_[1] << 8) | this->data_[2];
134 uint16_t confidence = ((uint16_t) this->data_[3] << 8) | this->data_[4];
135 if (this->last_finger_id_sensor_ != nullptr) {
136 this->last_finger_id_sensor_->publish_state(finger_id);
137 }
138 if (this->last_confidence_sensor_ != nullptr) {
139 this->last_confidence_sensor_->publish_state(confidence);
140 }
141 this->finger_scan_matched_callback_.call(finger_id, confidence);
142 break;
143 }
144 case NOT_FOUND:
145 ESP_LOGD(TAG, "Fingerprint not matched to any saved slots");
147 break;
148 }
149 }
150}
151
153 if (this->has_sensing_pin_) {
154 ESP_LOGD(TAG, "Getting image %d", buffer);
155 } else {
156 ESP_LOGV(TAG, "Getting image %d", buffer);
157 }
158 this->data_ = {GET_IMAGE};
159 uint8_t send_result = this->send_command_();
160 switch (send_result) {
161 case OK:
162 break;
163 case NO_FINGER:
164 if (this->has_sensing_pin_) {
165 this->waiting_removal_ = true;
166 ESP_LOGD(TAG, "Finger Misplaced");
168 } else {
169 ESP_LOGV(TAG, "No finger");
170 }
171 return send_result;
172 case IMAGE_FAIL:
173 ESP_LOGE(TAG, "Imaging error");
175 return send_result;
176 default:
177 ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result);
178 return send_result;
179 }
180
181 ESP_LOGD(TAG, "Processing image %d", buffer);
182 this->data_ = {IMAGE_2_TZ, buffer};
183 send_result = this->send_command_();
184 switch (send_result) {
185 case OK:
186 ESP_LOGI(TAG, "Processed image %d", buffer);
187 break;
188 case IMAGE_MESS:
189 ESP_LOGE(TAG, "Image too messy");
191 break;
192 case FEATURE_FAIL:
193 case INVALID_IMAGE:
194 ESP_LOGE(TAG, "Could not find fingerprint features");
196 break;
197 default:
198 break;
199 }
200 return send_result;
201}
202
204 ESP_LOGI(TAG, "Creating model");
205 this->data_ = {REG_MODEL};
206 switch (this->send_command_()) {
207 case OK:
208 break;
209 case ENROLL_MISMATCH:
210 ESP_LOGE(TAG, "Scans do not match");
211 [[fallthrough]];
212 default:
213 return this->data_[0];
214 }
215
216 ESP_LOGI(TAG, "Storing model");
217 this->data_ = {STORE, 0x01, (uint8_t) (this->enrollment_slot_ >> 8), (uint8_t) (this->enrollment_slot_ & 0xFF)};
218 switch (this->send_command_()) {
219 case OK:
220 ESP_LOGI(TAG, "Stored model");
221 break;
222 case BAD_LOCATION:
223 ESP_LOGE(TAG, "Invalid slot");
224 break;
225 case FLASH_ERR:
226 ESP_LOGE(TAG, "Error writing to flash");
227 break;
228 }
229 return this->data_[0];
230}
231
233 ESP_LOGD(TAG, "Checking password");
234 this->data_ = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
235 (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
236 switch (this->send_command_()) {
237 case OK:
238 ESP_LOGD(TAG, "Password verified");
239 return true;
240 case PASSWORD_FAIL:
241 ESP_LOGE(TAG, "Wrong password");
242 break;
243 }
244 return false;
245}
246
248 ESP_LOGI(TAG, "Setting new password: %" PRIu32, this->new_password_);
249 this->data_ = {SET_PASSWORD, (uint8_t) (this->new_password_ >> 24), (uint8_t) (this->new_password_ >> 16),
250 (uint8_t) (this->new_password_ >> 8), (uint8_t) (this->new_password_ & 0xFF)};
251 if (this->send_command_() == OK) {
252 ESP_LOGI(TAG, "New password successfully set");
253 ESP_LOGI(TAG, "Define the new password in your configuration and reflash now");
254 ESP_LOGW(TAG, "!!!Forgetting the password will render your device unusable!!!");
255 return true;
256 }
257 return false;
258}
259
261 ESP_LOGD(TAG, "Getting parameters");
262 this->data_ = {READ_SYS_PARAM};
263 if (this->send_command_() == OK) {
264 ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status,
265 if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1]
266 this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
267 }
268 this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4];
269 this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6];
270 if (this->capacity_sensor_ != nullptr) {
272 }
273 if (this->security_level_sensor_ != nullptr) {
274 this->security_level_sensor_->publish_state(((uint16_t) this->data_[7] << 8) | this->data_[8]);
275 }
276 if (this->enrolling_binary_sensor_ != nullptr) {
278 }
280 return true;
281 }
282 return false;
283}
284
286 ESP_LOGD(TAG, "Getting fingerprint count");
287 this->data_ = {TEMPLATE_COUNT};
288 if (this->send_command_() == OK) {
289 ESP_LOGD(TAG, "Got fingerprint count");
290 if (this->fingerprint_count_sensor_ != nullptr)
291 this->fingerprint_count_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
292 }
293}
294
296 ESP_LOGI(TAG, "Deleting fingerprint in slot %d", finger_id);
297 this->data_ = {DELETE, (uint8_t) (finger_id >> 8), (uint8_t) (finger_id & 0xFF), 0x00, 0x01};
298 switch (this->send_command_()) {
299 case OK:
300 ESP_LOGI(TAG, "Deleted fingerprint");
302 break;
303 case DELETE_FAIL:
304 ESP_LOGE(TAG, "Reader failed to delete fingerprint");
305 break;
306 }
307}
308
310 ESP_LOGI(TAG, "Deleting all stored fingerprints");
311 this->data_ = {DELETE_ALL};
312 switch (this->send_command_()) {
313 case OK:
314 ESP_LOGI(TAG, "Deleted all fingerprints");
316 break;
317 case DB_CLEAR_FAIL:
318 ESP_LOGE(TAG, "Reader failed to clear fingerprint library");
319 break;
320 }
321}
322
324 ESP_LOGD(TAG, "Setting LED");
325 if (state) {
326 this->data_ = {LED_ON};
327 } else {
328 this->data_ = {LED_OFF};
329 }
330 switch (this->send_command_()) {
331 case OK:
332 ESP_LOGD(TAG, "LED set");
333 break;
334 case PACKET_RCV_ERR:
335 case TIMEOUT:
336 break;
337 default:
338 ESP_LOGE(TAG, "Try aura_led_control instead");
339 break;
340 }
341}
342
343void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, uint8_t color, uint8_t count) {
344 const uint32_t now = millis();
345 const uint32_t elapsed = now - this->last_aura_led_control_;
346 if (elapsed < this->last_aura_led_duration_) {
347 delay(this->last_aura_led_duration_ - elapsed);
348 }
349 ESP_LOGD(TAG, "Setting Aura LED");
350 this->data_ = {AURA_CONFIG, state, speed, color, count};
351 switch (this->send_command_()) {
352 case OK:
353 ESP_LOGD(TAG, "Aura LED set");
354 this->last_aura_led_control_ = millis();
355 this->last_aura_led_duration_ = 10 * speed * count;
356 break;
357 case PACKET_RCV_ERR:
358 case TIMEOUT:
359 break;
360 default:
361 ESP_LOGE(TAG, "Try led_control instead");
362 break;
363 }
364}
365
366uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> &data_buffer) {
367 this->discard_available();
368 this->write((uint8_t) (START_CODE >> 8));
369 this->write((uint8_t) (START_CODE & 0xFF));
370 this->write(this->address_[0]);
371 this->write(this->address_[1]);
372 this->write(this->address_[2]);
373 this->write(this->address_[3]);
374 this->write(COMMAND);
375
376 uint16_t wire_length = data_buffer.size() + 2;
377 this->write((uint8_t) (wire_length >> 8));
378 this->write((uint8_t) (wire_length & 0xFF));
379
380 uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
381 for (auto data : data_buffer) {
382 this->write(data);
383 sum += data;
384 }
385
386 this->write((uint8_t) (sum >> 8));
387 this->write((uint8_t) (sum & 0xFF));
388
389 data_buffer.clear();
390
391 uint8_t byte;
392 uint16_t idx = 0, length = 0;
393
394 for (uint16_t timer = 0; timer < 1000; timer++) {
395 if (this->available() == 0) {
396 delay(1);
397 continue;
398 }
399
400 byte = this->read();
401
402 switch (idx) {
403 case 0:
404 if (byte != (uint8_t) (START_CODE >> 8))
405 continue;
406 break;
407 case 1:
408 if (byte != (uint8_t) (START_CODE & 0xFF)) {
409 idx = 0;
410 continue;
411 }
412 break;
413 case 2:
414 case 3:
415 case 4:
416 case 5:
417 if (byte != this->address_[idx - 2]) {
418 idx = 0;
419 continue;
420 }
421 break;
422 case 6:
423 if (byte != ACK) {
424 idx = 0;
425 continue;
426 }
427 break;
428 case 7:
429 length = (uint16_t) byte << 8;
430 break;
431 case 8:
432 length |= byte;
433 break;
434 default:
435 data_buffer.push_back(byte);
436 if ((idx - 8) == length) {
437 switch (data_buffer[0]) {
438 case OK:
439 case NO_FINGER:
440 case IMAGE_FAIL:
441 case IMAGE_MESS:
442 case FEATURE_FAIL:
443 case NO_MATCH:
444 case NOT_FOUND:
445 case ENROLL_MISMATCH:
446 case BAD_LOCATION:
447 case DELETE_FAIL:
448 case DB_CLEAR_FAIL:
449 case PASSWORD_FAIL:
450 case INVALID_IMAGE:
451 case FLASH_ERR:
452 break;
453 case PACKET_RCV_ERR:
454 ESP_LOGE(TAG, "Reader failed to process request");
455 break;
456 default:
457 ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", data_buffer[0]);
458 break;
459 }
460 this->last_transfer_ms_ = millis();
461 return data_buffer[0];
462 }
463 break;
464 }
465 idx++;
466 }
467 ESP_LOGE(TAG, "No response received from reader");
468 data_buffer.clear();
469 data_buffer.push_back(TIMEOUT);
470 this->last_transfer_ms_ = millis();
471 return TIMEOUT;
472}
473
475 this->sensor_wakeup_();
476 return this->transfer_(this->data_);
477}
478
480 // Immediately return if there is no power pin or the sensor is already on
481 if ((!this->has_power_pin_) || (this->is_sensor_awake_))
482 return;
483
484 this->sensor_power_pin_->digital_write(true);
485 this->is_sensor_awake_ = true;
486
487 uint8_t byte = TIMEOUT;
488
489 // Wait for the byte HANDSHAKE_SIGN from the sensor meaning it is operational.
490 for (uint16_t timer = 0; timer < WAIT_FOR_WAKE_UP_MS; timer++) {
491 if (this->available() > 0) {
492 byte = this->read();
493
494 /* If the received byte is zero, the UART probably misinterpreted a raising edge on
495 * the RX pin due the power up as byte "zero" - I verified this behaviour using
496 * the esp32-arduino lib. So here we just ignore this fake byte.
497 */
498 if (byte != 0)
499 break;
500 }
501 delay(1);
502 }
503
504 /* Lets check if the received by is a HANDSHAKE_SIGN, otherwise log an error
505 * message and try to continue on the best effort.
506 */
507 if (byte == HANDSHAKE_SIGN) {
508 ESP_LOGD(TAG, "Sensor has woken up!");
509 } else if (byte == TIMEOUT) {
510 ESP_LOGE(TAG, "Timed out waiting for sensor wake-up");
511 } else {
512 ESP_LOGE(TAG, "Received wrong byte from the sensor during wake-up: 0x%.2X", byte);
513 }
514
515 /* Next step, we must authenticate with the password. We cannot call check_password_ here
516 * neither use data_ to store the command because it might be already in use by the caller
517 * of send_command_()
518 */
519 std::vector<uint8_t> buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
520 (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
521
522 if (this->transfer_(buffer) != OK) {
523 ESP_LOGE(TAG, "Wrong password");
524 }
525}
526
528 // Immediately return if the power pin feature is not implemented
529 if (!this->has_power_pin_)
530 return;
531
532 this->sensor_power_pin_->digital_write(false);
533 this->is_sensor_awake_ = false;
534 ESP_LOGD(TAG, "Fingerprint sensor is now in sleep mode.");
535}
536
538 char sensing_pin_buf[GPIO_SUMMARY_MAX_LEN];
539 char power_pin_buf[GPIO_SUMMARY_MAX_LEN];
540 if (this->has_sensing_pin_) {
541 this->sensing_pin_->dump_summary(sensing_pin_buf, sizeof(sensing_pin_buf));
542 }
543 if (this->has_power_pin_) {
544 this->sensor_power_pin_->dump_summary(power_pin_buf, sizeof(power_pin_buf));
545 }
546 ESP_LOGCONFIG(TAG,
547 "GROW_FINGERPRINT_READER:\n"
548 " System Identifier Code: 0x%.4X\n"
549 " Touch Sensing Pin: %s\n"
550 " Sensor Power Pin: %s",
551 this->system_identifier_code_, this->has_sensing_pin_ ? sensing_pin_buf : LOG_STR_LITERAL("None"),
552 this->has_power_pin_ ? power_pin_buf : LOG_STR_LITERAL("None"));
553 if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
554 ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
555 } else {
556 ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
557 }
558 LOG_UPDATE_INTERVAL(this);
559 if (this->fingerprint_count_sensor_) {
560 LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
561 ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
562 }
563 if (this->status_sensor_) {
564 LOG_SENSOR(" ", "Status", this->status_sensor_);
565 ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
566 }
567 if (this->capacity_sensor_) {
568 LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
569 ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
570 }
571 if (this->security_level_sensor_) {
572 LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
573 ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
574 }
575 if (this->last_finger_id_sensor_) {
576 LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
577 ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
578 }
579 if (this->last_confidence_sensor_) {
580 LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
581 ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
582 }
583}
584
585} // namespace esphome::fingerprint_grow
void mark_failed()
Mark this component as failed.
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
virtual size_t dump_summary(char *buffer, size_t len) const
Write a summary of this pin to the provided buffer.
Definition gpio_pin.h:126
void publish_state(bool new_state)
Publish a new state to the front-end.
CallbackManager< void(uint16_t, uint16_t)> finger_scan_matched_callback_
uint8_t transfer_(std::vector< uint8_t > &data_buffer)
void enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers)
CallbackManager< void(uint16_t)> enrollment_done_callback_
CallbackManager< void(uint16_t)> enrollment_failed_callback_
void aura_led_control(uint8_t state, uint8_t speed, uint8_t color, uint8_t count)
CallbackManager< void(uint8_t, uint16_t)> enrollment_scan_callback_
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
float get_state() const
Getter-syntax for .state.
Definition sensor.h:98
void discard_available()
Read and drop every byte waiting in the receive buffer.
Definition uart.cpp:36
size_t write(uint8_t data)
Definition uart.h:105
int speed
Definition fan.h:3
bool state
Definition fan.h:2
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "fingerprint_grow")
constexpr size_t GPIO_SUMMARY_MAX_LEN
Maximum buffer size for dump_summary output.
Definition gpio_pin.h:14
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t