Repository navigation
External Keypads ESPHome
Part of External Keypads, which explains the use_credential action these recipes call and what a valid code produces.
On the device, publish the code and clear it (clearing is what lets the same code be entered twice in a row):
key_collector:
- id: pin_collector
source_id: keypad
min_length: 4
max_length: 8
end_keys: "#"
end_key_required: true
on_result:
- text_sensor.template.publish:
id: keypad_code
state: !lambda 'return x;'
- delay: 1s
- text_sensor.template.publish: {id: keypad_code, state: ""}In Home Assistant:
automation:
- alias: "Garage: check keypad codes"
triggers:
- trigger: state
entity_id: sensor.keypad_code
not_to: [unknown, unavailable, ""]
actions:
- action: lock_code_manager.use_credential
data:
config_entry_title: House Locks
code: "{{ trigger.to_state.state }}"
source: sensor.keypad_code
target: lock.garage
response_variable: check
- if: "{{ check.valid }}"
then:
- action: cover.open_cover
target: {entity_id: cover.garage_door}
not_to: [unknown, unavailable, ""]is not optional — without it, a Wi-Fi reconnect that republishes the device's last value runs this with nobody at the keypad.
ESPHome 2025.10+ can capture an action's response, so the device decides locally and the code never becomes an entity state at all.
on_result:
- homeassistant.action:
action: lock_code_manager.use_credential
data_template:
config_entry_title: House Locks
code: "{{ entered }}"
source: binary_sensor.garage_keypad_status
target: lock.garage
variables:
entered: !lambda 'return x;'
capture_response: true
on_success:
- if:
condition:
lambda: 'return response["valid"].as<bool>();'
then:
- switch.turn_on: garage_relay
- rtttl.play: "ok:d=4,o=5,b=100:c6"
else:
- rtttl.play: "no:d=4,o=5,b=100:c4,c4"source still has to name an entity — any entity belonging to the keypad will
do.
- choose:
- conditions: "{{ check.valid }}"
sequence:
- action: tts.speak
data: {message: "Welcome, {{ check.user }}."}
- conditions: "{{ check.reason == 'condition_not_met' }}"
sequence:
- action: tts.speak
data: {message: "That code isn't valid right now."}
- conditions: "{{ check.reason == 'user_disabled' }}"
sequence:
- action: tts.speak
data: {message: "That code has been turned off."}
default:
- action: tts.speak
data: {message: "Code not recognized."} on_result:
- homeassistant.event:
event: esphome.keypad_code_entered # must start with "esphome."
data_template:
code: "{{ entered }}"
variables:
entered: !lambda 'return x;'Trigger on esphome.keypad_code_entered and pass
trigger.event.data.code to the action.
Getting Started
- Upgrading to 7.0
- Upgrading to 6.0
- Upgrading to 5.0
- Configuration Structure
- Adding and Removing Locks
- Migrating from keymaster
UI
Features
- Managing Guests and Rentals
- Services and Actions
- Blueprints
- Tracking lock state change events
- Using Condition Entities
- Unsupported Condition Entities
Advanced
Development
Troubleshooting
FAQ
Supported Integrations