ESPHome  2024.12.2
fingerprint_grow.cpp
Go to the documentation of this file.
1 #include "fingerprint_grow.h"
2 #include "esphome/core/log.h"
3 #include <cinttypes>
4 
5 namespace esphome {
6 namespace fingerprint_grow {
7 
8 static const char *const 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_) {
14  this->finish_enrollment(this->save_fingerprint_());
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
24  (millis() - this->last_transfer_ms_ > this->idle_period_to_sleep_ms_) && (this->is_sensor_awake_)) {
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  ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader...");
61 
62  this->has_sensing_pin_ = (this->sensing_pin_ != nullptr);
63  this->has_power_pin_ = (this->sensor_power_pin_ != nullptr);
64 
65  // Call pins setup, so we effectively apply the config generated from the yaml file.
66  if (this->has_sensing_pin_) {
67  this->sensing_pin_->setup();
68  }
69  if (this->has_power_pin_) {
70  // Starts with output low (disabling power) to avoid glitches in the sensor
71  this->sensor_power_pin_->digital_write(false);
72  this->sensor_power_pin_->setup();
73 
74  // If the user didn't specify an idle period to sleep, applies the default.
75  if (this->idle_period_to_sleep_ms_ == UINT32_MAX) {
76  this->idle_period_to_sleep_ms_ = DEFAULT_IDLE_PERIOD_TO_SLEEP_MS;
77  }
78  }
79 
80  // Place the sensor in a known (sleep/off) state and sync internal var state.
81  this->sensor_sleep_();
82  delay(20); // This delay guarantees the sensor will in fact be powered power.
83 
84  if (this->check_password_()) {
85  if (this->new_password_ != -1) {
86  if (this->set_password_())
87  return;
88  } else {
89  if (this->get_parameters_())
90  return;
91  }
92  }
93  this->mark_failed();
94 }
95 
96 void FingerprintGrowComponent::enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers) {
97  ESP_LOGI(TAG, "Starting enrollment in slot %d", finger_id);
98  if (this->enrolling_binary_sensor_ != nullptr) {
100  }
101  this->enrollment_slot_ = finger_id;
102  this->enrollment_buffers_ = num_buffers;
103  this->enrollment_image_ = 1;
104 }
105 
107  if (result == OK) {
109  this->get_fingerprint_count_();
110  } else {
111  if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
113  }
114  }
115  this->enrollment_image_ = 0;
116  this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
117  if (this->enrolling_binary_sensor_ != nullptr) {
119  }
120  ESP_LOGI(TAG, "Finished enrollment");
121 }
122 
124  if (this->has_sensing_pin_) {
125  ESP_LOGD(TAG, "Scan and match");
126  } else {
127  ESP_LOGV(TAG, "Scan and match");
128  }
129  if (this->scan_image_(1) == OK) {
130  this->waiting_removal_ = true;
131  this->data_ = {SEARCH, 0x01, 0x00, 0x00, (uint8_t) (this->capacity_ >> 8), (uint8_t) (this->capacity_ & 0xFF)};
132  switch (this->send_command_()) {
133  case OK: {
134  ESP_LOGD(TAG, "Fingerprint matched");
135  uint16_t finger_id = ((uint16_t) this->data_[1] << 8) | this->data_[2];
136  uint16_t confidence = ((uint16_t) this->data_[3] << 8) | this->data_[4];
137  if (this->last_finger_id_sensor_ != nullptr) {
138  this->last_finger_id_sensor_->publish_state(finger_id);
139  }
140  if (this->last_confidence_sensor_ != nullptr) {
141  this->last_confidence_sensor_->publish_state(confidence);
142  }
143  this->finger_scan_matched_callback_.call(finger_id, confidence);
144  break;
145  }
146  case NOT_FOUND:
147  ESP_LOGD(TAG, "Fingerprint not matched to any saved slots");
148  this->finger_scan_unmatched_callback_.call();
149  break;
150  }
151  }
152 }
153 
154 uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
155  if (this->has_sensing_pin_) {
156  ESP_LOGD(TAG, "Getting image %d", buffer);
157  } else {
158  ESP_LOGV(TAG, "Getting image %d", buffer);
159  }
160  this->data_ = {GET_IMAGE};
161  uint8_t send_result = this->send_command_();
162  switch (send_result) {
163  case OK:
164  break;
165  case NO_FINGER:
166  if (this->has_sensing_pin_) {
167  this->waiting_removal_ = true;
168  ESP_LOGD(TAG, "Finger Misplaced");
169  this->finger_scan_misplaced_callback_.call();
170  } else {
171  ESP_LOGV(TAG, "No finger");
172  }
173  return send_result;
174  case IMAGE_FAIL:
175  ESP_LOGE(TAG, "Imaging error");
176  this->finger_scan_invalid_callback_.call();
177  return send_result;
178  default:
179  ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result);
180  return send_result;
181  }
182 
183  ESP_LOGD(TAG, "Processing image %d", buffer);
184  this->data_ = {IMAGE_2_TZ, buffer};
185  send_result = this->send_command_();
186  switch (send_result) {
187  case OK:
188  ESP_LOGI(TAG, "Processed image %d", buffer);
189  break;
190  case IMAGE_MESS:
191  ESP_LOGE(TAG, "Image too messy");
192  this->finger_scan_invalid_callback_.call();
193  break;
194  case FEATURE_FAIL:
195  case INVALID_IMAGE:
196  ESP_LOGE(TAG, "Could not find fingerprint features");
197  this->finger_scan_invalid_callback_.call();
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  default:
212  return this->data_[0];
213  }
214 
215  ESP_LOGI(TAG, "Storing model");
216  this->data_ = {STORE, 0x01, (uint8_t) (this->enrollment_slot_ >> 8), (uint8_t) (this->enrollment_slot_ & 0xFF)};
217  switch (this->send_command_()) {
218  case OK:
219  ESP_LOGI(TAG, "Stored model");
220  break;
221  case BAD_LOCATION:
222  ESP_LOGE(TAG, "Invalid slot");
223  break;
224  case FLASH_ERR:
225  ESP_LOGE(TAG, "Error writing to flash");
226  break;
227  }
228  return this->data_[0];
229 }
230 
232  ESP_LOGD(TAG, "Checking password");
233  this->data_ = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
234  (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
235  switch (this->send_command_()) {
236  case OK:
237  ESP_LOGD(TAG, "Password verified");
238  return true;
239  case PASSWORD_FAIL:
240  ESP_LOGE(TAG, "Wrong password");
241  break;
242  }
243  return false;
244 }
245 
247  ESP_LOGI(TAG, "Setting new password: %" PRIu32, this->new_password_);
248  this->data_ = {SET_PASSWORD, (uint8_t) (this->new_password_ >> 24), (uint8_t) (this->new_password_ >> 16),
249  (uint8_t) (this->new_password_ >> 8), (uint8_t) (this->new_password_ & 0xFF)};
250  if (this->send_command_() == OK) {
251  ESP_LOGI(TAG, "New password successfully set");
252  ESP_LOGI(TAG, "Define the new password in your configuration and reflash now");
253  ESP_LOGW(TAG, "!!!Forgetting the password will render your device unusable!!!");
254  return true;
255  }
256  return false;
257 }
258 
260  ESP_LOGD(TAG, "Getting parameters");
261  this->data_ = {READ_SYS_PARAM};
262  if (this->send_command_() == OK) {
263  ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status,
264  if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1]
265  this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
266  }
267  this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4];
268  this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6];
269  if (this->capacity_sensor_ != nullptr) {
271  }
272  if (this->security_level_sensor_ != nullptr) {
273  this->security_level_sensor_->publish_state(((uint16_t) this->data_[7] << 8) | this->data_[8]);
274  }
275  if (this->enrolling_binary_sensor_ != nullptr) {
277  }
278  this->get_fingerprint_count_();
279  return true;
280  }
281  return false;
282 }
283 
285  ESP_LOGD(TAG, "Getting fingerprint count");
286  this->data_ = {TEMPLATE_COUNT};
287  if (this->send_command_() == OK) {
288  ESP_LOGD(TAG, "Got fingerprint count");
289  if (this->fingerprint_count_sensor_ != nullptr)
290  this->fingerprint_count_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
291  }
292 }
293 
295  ESP_LOGI(TAG, "Deleting fingerprint in slot %d", finger_id);
296  this->data_ = {DELETE, (uint8_t) (finger_id >> 8), (uint8_t) (finger_id & 0xFF), 0x00, 0x01};
297  switch (this->send_command_()) {
298  case OK:
299  ESP_LOGI(TAG, "Deleted fingerprint");
300  this->get_fingerprint_count_();
301  break;
302  case DELETE_FAIL:
303  ESP_LOGE(TAG, "Reader failed to delete fingerprint");
304  break;
305  }
306 }
307 
309  ESP_LOGI(TAG, "Deleting all stored fingerprints");
310  this->data_ = {DELETE_ALL};
311  switch (this->send_command_()) {
312  case OK:
313  ESP_LOGI(TAG, "Deleted all fingerprints");
314  this->get_fingerprint_count_();
315  break;
316  case DB_CLEAR_FAIL:
317  ESP_LOGE(TAG, "Reader failed to clear fingerprint library");
318  break;
319  }
320 }
321 
323  ESP_LOGD(TAG, "Setting LED");
324  if (state) {
325  this->data_ = {LED_ON};
326  } else {
327  this->data_ = {LED_OFF};
328  }
329  switch (this->send_command_()) {
330  case OK:
331  ESP_LOGD(TAG, "LED set");
332  break;
333  case PACKET_RCV_ERR:
334  case TIMEOUT:
335  break;
336  default:
337  ESP_LOGE(TAG, "Try aura_led_control instead");
338  break;
339  }
340 }
341 
342 void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, uint8_t color, uint8_t count) {
343  const uint32_t now = millis();
344  const uint32_t elapsed = now - this->last_aura_led_control_;
345  if (elapsed < this->last_aura_led_duration_) {
346  delay(this->last_aura_led_duration_ - elapsed);
347  }
348  ESP_LOGD(TAG, "Setting Aura LED");
349  this->data_ = {AURA_CONFIG, state, speed, color, count};
350  switch (this->send_command_()) {
351  case OK:
352  ESP_LOGD(TAG, "Aura LED set");
353  this->last_aura_led_control_ = millis();
354  this->last_aura_led_duration_ = 10 * speed * count;
355  break;
356  case PACKET_RCV_ERR:
357  case TIMEOUT:
358  break;
359  default:
360  ESP_LOGE(TAG, "Try led_control instead");
361  break;
362  }
363 }
364 
365 uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer) {
366  while (this->available())
367  this->read();
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 = p_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 : *p_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  p_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  p_data_buffer->push_back(byte);
436  if ((idx - 8) == length) {
437  switch ((*p_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", (*p_data_buffer)[0]);
458  break;
459  }
460  this->last_transfer_ms_ = millis();
461  return (*p_data_buffer)[0];
462  }
463  break;
464  }
465  idx++;
466  }
467  ESP_LOGE(TAG, "No response received from reader");
468  (*p_data_buffer)[0] = TIMEOUT;
469  this->last_transfer_ms_ = millis();
470  return TIMEOUT;
471 }
472 
474  this->sensor_wakeup_();
475  return this->transfer_(&this->data_);
476 }
477 
479  // Immediately return if there is no power pin or the sensor is already on
480  if ((!this->has_power_pin_) || (this->is_sensor_awake_))
481  return;
482 
483  this->sensor_power_pin_->digital_write(true);
484  this->is_sensor_awake_ = true;
485 
486  uint8_t byte = TIMEOUT;
487 
488  // Wait for the byte HANDSHAKE_SIGN from the sensor meaning it is operational.
489  for (uint16_t timer = 0; timer < WAIT_FOR_WAKE_UP_MS; timer++) {
490  if (this->available() > 0) {
491  byte = this->read();
492 
493  /* If the received byte is zero, the UART probably misinterpreted a raising edge on
494  * the RX pin due the power up as byte "zero" - I verified this behaviour using
495  * the esp32-arduino lib. So here we just ignore this fake byte.
496  */
497  if (byte != 0)
498  break;
499  }
500  delay(1);
501  }
502 
503  /* Lets check if the received by is a HANDSHAKE_SIGN, otherwise log an error
504  * message and try to continue on the best effort.
505  */
506  if (byte == HANDSHAKE_SIGN) {
507  ESP_LOGD(TAG, "Sensor has woken up!");
508  } else if (byte == TIMEOUT) {
509  ESP_LOGE(TAG, "Timed out waiting for sensor wake-up");
510  } else {
511  ESP_LOGE(TAG, "Received wrong byte from the sensor during wake-up: 0x%.2X", byte);
512  }
513 
514  /* Next step, we must authenticate with the password. We cannot call check_password_ here
515  * neither use data_ to store the command because it might be already in use by the caller
516  * of send_command_()
517  */
518  std::vector<uint8_t> buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
519  (uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
520 
521  if (this->transfer_(&buffer) != OK) {
522  ESP_LOGE(TAG, "Wrong password");
523  }
524 }
525 
527  // Immediately return if the power pin feature is not implemented
528  if (!this->has_power_pin_)
529  return;
530 
531  this->sensor_power_pin_->digital_write(false);
532  this->is_sensor_awake_ = false;
533  ESP_LOGD(TAG, "Fingerprint sensor is now in sleep mode.");
534 }
535 
537  ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:");
538  ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_);
539  ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s",
540  this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None");
541  ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
542  this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
543  if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
544  ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
545  } else {
546  ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
547  }
548  LOG_UPDATE_INTERVAL(this);
549  if (this->fingerprint_count_sensor_) {
550  LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
551  ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
552  }
553  if (this->status_sensor_) {
554  LOG_SENSOR(" ", "Status", this->status_sensor_);
555  ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
556  }
557  if (this->capacity_sensor_) {
558  LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
559  ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
560  }
561  if (this->security_level_sensor_) {
562  LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
563  ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
564  }
565  if (this->last_finger_id_sensor_) {
566  LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
567  ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
568  }
569  if (this->last_confidence_sensor_) {
570  LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
571  ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
572  }
573 }
574 
575 } // namespace fingerprint_grow
576 } // namespace esphome
virtual void digital_write(bool value)=0
CallbackManager< void(uint16_t, uint16_t)> finger_scan_matched_callback_
CallbackManager< void(uint8_t, uint16_t)> enrollment_scan_callback_
int speed
Definition: fan.h:35
virtual void setup()=0
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
virtual std::string dump_summary() const =0
uint8_t transfer_(std::vector< uint8_t > *p_data_buffer)
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void publish_state(bool state)
Publish a new state to the front-end.
void enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers)
CallbackManager< void(uint16_t)> enrollment_failed_callback_
virtual bool digital_read()=0
binary_sensor::BinarySensor * enrolling_binary_sensor_
float get_state() const
Getter-syntax for .state.
Definition: sensor.cpp:86
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
uint16_t length
Definition: tt21100.cpp:12
CallbackManager< void(uint16_t)> enrollment_done_callback_
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void aura_led_control(uint8_t state, uint8_t speed, uint8_t color, uint8_t count)
size_t write(uint8_t data)
Definition: uart.h:52
bool state
Definition: fan.h:34
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26