# ESP32 + R503 Fingerprint Reader → LOQED via ESPHome & Homey

**URL:** <https://community.homey.app/t/esp32-r503-fingerprint-reader-loqed-via-esphome-homey/158497>\
**Category:** Tutorials\
**Created:** [August 19, 2026, 6:56pm UTC](https://community.homey.app/t/esp32-r503-fingerprint-reader-loqed-via-esphome-homey/158497 "2026-08-19T18:56:27Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Benny\_VanWynsberghe](https://sea1.discourse-cdn.com/flex025/user_avatar/community.homey.app/benny_vanwynsberghe/32/168117_2.png) [@Benny\_VanWynsberghe](https://community.homey.app/u/Benny_VanWynsberghe)\
**Post date:** [August 19, 2026, 6:56pm UTC](https://community.homey.app/t/esp32-r503-fingerprint-reader-loqed-via-esphome-homey/158497/1 "2026-08-19T18:56:27Z")

</div>

Hey everyone,

I wanted to share a small DIY project I recently finished: a fingerprint reader built on an ESP32 with ESPHome, which unlocks my LOQED Touch Smart Lock through a Homey Flow. No cloud dependency, no proprietary app — just ESPHome talking to Homey over the native API.

**What it does**

- Enroll fingerprints from a button in Homey (or from a built-in web dashboard)
- Recognizes an enrolled finger and briefly triggers an “Access Granted” sensor
- That sensor drives a Homey Flow that unlocks the smart lock
- A live status log (readable/unmatched/misplaced/etc.) shows up both in Homey and on a simple web dashboard on the device’s own IP
- Adjustable match sensitivity (the R503’s security level, 1–5) via two extra buttons, since ESPHome doesn’t expose that setting out of the box

**Hardware**

- ESP32 dev board
- Grow R503 capacitive fingerprint sensor (6-pin JST)
- A decent 5V/2A USB power source (a laptop USB port isn’t always enough — the sensor plus WiFi TX spikes can brown out a weak supply)

**Wiring**

| **R503 pin** | **Name** | **ESP32 pin** | **Notes** |
| --- | --- | --- | --- |
| 1 | VCC | 3V3 | Check your module’s silkscreen — some R503 variants need 3.6–6V instead of 3.3V |
| 2 | GND | GND | |
| 3 | TXD | GPIO16 (RX2) | Crossed: sensor TX → ESP32 RX |
| 4 | RXD | GPIO17 (TX2) | Crossed: sensor RX → ESP32 TX |
| 5 | WAKEUP | GPIO4 | Important — without this wired, ESPHome’s `sensing_pin` gate silently skips all polling and the sensor never detects a finger at all |
| 6 | 3.3VT (touch power) | 3V3 | Separate power line for the capacitive touch/wake circuit |

That WAKEUP pin (5) tripped me up for a while — without it, the sensor compiles and boots fine, reports its capacity correctly, but simply never reacts to a finger. If you’re debugging a “nothing happens” symptom, check that wire first.

**Software / setup**

1. Flash with ESPHome (`python -m esphome run fingerprint-lock.yaml`, USB the first time, OTA afterwards)
2. Add each entity in Homey via the **ESPHome Controller** app (IP + port 6053 + the API encryption key)
3. Build a Flow: WHEN “Access Granted” turns ON → THEN unlock the smart lock

Full yaml config, a `secrets.yaml` template, and a more detailed README are attached below if anyone wants to replicate this or adapt it for a different lock/sensor combo. Happy to answer questions if you get stuck on the wiring or the ESPHome side.

fingerprint-lock\_14.yaml

```yaml
esphome:
  name: fingerprint-lock
  friendly_name: Fingerprint Reader

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome
    password: !secret ota_password

logger:

web_server:
  port: 80

uart:
  id: uart_bus
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 57600

fingerprint_grow:
  id: fingerprint_reader
  uart_id: uart_bus
  sensing_pin: GPIO4 # connect to WAKEUP (pin 5) of the R503
  on_finger_scan_start:
    - text_sensor.template.publish:
        id: access_log
        state: "Finger detected, scanning..."
  on_finger_scan_matched:
    - logger.log:
        format: "Match! ID %d, confidence %d"
        args: ['finger_id', 'confidence']
    - text_sensor.template.publish:
        id: access_log
        state: !lambda |-
          char buf[64];
          snprintf(buf, sizeof(buf), "Access granted - ID %d (confidence %d)", finger_id, confidence);
          return std::string(buf);
    - binary_sensor.template.publish:
        id: access_granted
        state: ON
    - delay: 1s
    - binary_sensor.template.publish:
        id: access_granted
        state: OFF
  on_finger_scan_unmatched:
    - logger.log: "No match found"
    - text_sensor.template.publish:
        id: access_log
        state: "Unknown finger - access denied"
  on_finger_scan_invalid:
    - logger.log: "Invalid scan"
    - text_sensor.template.publish:
        id: access_log
        state: "Invalid scan - try again"
  on_finger_scan_misplaced:
    - text_sensor.template.publish:
        id: access_log
        state: "Finger misplaced - try again"
  on_enrollment_scan:
    - logger.log:
        format: "Enrollment scan, step %d"
        args: ['scan_num']
    - sensor.template.publish:
        id: enroll_status
        state: !lambda "return scan_num;"
    - text_sensor.template.publish:
        id: enroll_message
        state: !lambda |-
          char buf[64];
          if (scan_num < 2) {
            snprintf(buf, sizeof(buf), "Scan %d ok - place your finger again", scan_num);
          } else {
            snprintf(buf, sizeof(buf), "Scan %d ok - please wait...", scan_num);
          }
          return std::string(buf);
  on_enrollment_done:
    - logger.log:
        format: "Finger successfully enrolled with ID %d"
        args: ['finger_id']
    - sensor.template.publish:
        id: enroll_status
        state: 100
    - text_sensor.template.publish:
        id: enroll_message
        state: !lambda |-
          char buf[48];
          snprintf(buf, sizeof(buf), "Done! Finger enrolled (ID %d)", finger_id);
          return std::string(buf);
  on_enrollment_failed:
    - logger.log: "Enrollment failed"
    - sensor.template.publish:
        id: enroll_status
        state: -1
    - text_sensor.template.publish:
        id: enroll_message
        state: "Failed - try again"

text_sensor:
  - platform: template
    name: "Enroll Message"
    id: enroll_message
  - platform: template
    name: "Access Log"
    id: access_log

binary_sensor:
  - platform: template
    name: "Access Granted"
    id: access_granted

sensor:
  - platform: fingerprint_grow
    fingerprint_count:
      name: "Fingerprint Count"
    status:
      name: "Fingerprint Status"
    capacity:
      name: "Fingerprint Capacity"
    security_level:
      name: "Security Level"
  - platform: template
    name: "Enroll Status"
    id: enroll_status
    accuracy_decimals: 0
    icon: "mdi:fingerprint"
    # 0 = idle, 1-2 = scan X of 2 done, 100 = success, -1 = failed

button:
  - platform: restart
    name: "Restart"
  - platform: template
    name: "Enroll Fingerprint"
    on_press:
      - sensor.template.publish:
          id: enroll_status
          state: 0
      - text_sensor.template.publish:
          id: enroll_message
          state: "Starting... place your finger on the sensor"
      - fingerprint_grow.enroll:
          finger_id: 1
          num_scans: 2
  - platform: template
    name: "Cancel Enroll"
    on_press:
      - fingerprint_grow.cancel_enroll:
  - platform: template
    name: "Delete All Fingerprints"
    on_press:
      - fingerprint_grow.delete_all:
  - platform: template
    name: "Set Security Level Lenient (2)"
    on_press:
      - lambda: |-
          // Raw R503/AS608 command: PS_SetSysPara (0x0E), parameter 5 = security level, value 2
          uint8_t packet[] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x05, 0x0E, 0x05, 0x02, 0x00, 0x1B};
          id(uart_bus).write_array(packet, sizeof(packet));
  - platform: template
    name: "Set Security Level Default (3)"
    on_press:
      - lambda: |-
          // Raw R503/AS608 command: PS_SetSysPara (0x0E), parameter 5 = security level, value 3
          uint8_t packet[] = {0xEF, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x05, 0x0E, 0x05, 0x03, 0x00, 0x1C};
          id(uart_bus).write_array(packet, sizeof(packet));

```

secrets.yaml.example

```yaml
wifi_ssid: "YOUR_WIFI_SSID"
wifi_password: "YOUR_WIFI_PASSWORD"
api_key: "GENERATE_WITH: python -c \"import base64, os; print(base64.b64encode(os.urandom(32)).decode())\""
ota_password: "choose_your_own_ota_password"

```
