Skip to content

Instantly share code, notes, and snippets.

@mpflaga
Created March 18, 2026 22:50
Show Gist options
  • Select an option

  • Save mpflaga/4e57101bfbf8f8bc80156b2886df9f1e to your computer and use it in GitHub Desktop.

Select an option

Save mpflaga/4e57101bfbf8f8bc80156b2886df9f1e to your computer and use it in GitHub Desktop.
ESPHome Wiegand Pin Pad
# Garage Door Keypad - ESPHome Wiegand Reader
#
# A "thin edge" device that reads input from a Wiegand keypad/RFID reader
# and relays it to Home Assistant via a text_sensor. No PIN validation,
# secret storage, or access-control logic runs on the device itself —
# all authorization decisions live in Home Assistant automations.
#
# How it works:
# - Wiegand component reads key presses, tag scans, and raw data on D0/D1
# - key_collector assembles presses into a 4-digit PIN (# to enter, *# to clear)
# - Completed PINs and scanned tags are published to the "Keypad Code" sensor
# - Sensor clears after 500ms to prevent duplicate processing
#
# Network config is included from .network.yaml to keep credentials
# out of version control.
substitutions:
esphome_name: garage-door-keypad
friendly_name: Garage Door Keypad
# Pin definitions
wiegand_d0_pin: GPIO33
wiegand_d1_pin: GPIO35
# Keypad configuration
pin_length: "4"
pin_timeout: 4s
packages:
network: !include .network.yaml
esphome:
name: ${esphome_name}
friendly_name: ${friendly_name}
platformio_options:
board_build.f_cpu: 160000000L
logger:
level: INFO # Changed from DEBUG for production use
esp32:
board: esp32dev
framework:
type: arduino
wiegand:
- id: mykeypad
d0: ${wiegand_d0_pin}
d1: ${wiegand_d1_pin}
on_key:
- logger.log:
format: "Received key: %d"
args: ['x']
level: DEBUG
on_tag:
- logger.log:
format: "Received tag: %s"
args: ['x.c_str()']
level: INFO
- text_sensor.template.publish:
id: keypad_code
state: !lambda |-
return x;
on_raw:
- logger.log:
format: "Received raw: %d bits, value %llx"
args: ['bits', 'value']
level: DEBUG
key_collector:
- id: pincode_reader
source_id: mykeypad
min_length: ${pin_length}
max_length: ${pin_length}
end_keys: "#"
clear_keys: "*#"
allowed_keys: "0123456789"
timeout: ${pin_timeout}
on_progress:
- logger.log:
format: "PIN progress: '%s', started by '%c'"
args: ['x.c_str()', "(start == 0 ? '~' : start)"]
level: DEBUG
on_result:
- logger.log:
format: "PIN entered: '%s', started by '%c', ended by '%c'"
args: ['x.c_str()', "(start == 0 ? '~' : start)", "(end == 0 ? '~' : end)"]
level: INFO
- text_sensor.template.publish:
id: keypad_code
state: !lambda |-
return x;
# Allow time for value to be transmitted before clearing
- delay: 500ms
# Clear the sensor to prevent retransmission
- text_sensor.template.publish:
id: keypad_code
state: "0"
on_timeout:
- logger.log:
format: "PIN timeout: '%s', started by '%c'"
args: ['x.c_str()', "(start == 0 ? '~' : start)"]
level: WARN
text_sensor:
- platform: template
name: "Keypad Code"
id: keypad_code
icon: mdi:dialpad
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment