[sx1509] Keep the keypad keys in flash (#20111)

This commit is contained in:
J. Nick Koston
2026-10-05 19:23:39 -05:00
committed by GitHub
parent 039f07d332
commit 4097f45677
6 changed files with 66 additions and 6 deletions
+9 -2
View File
@@ -43,6 +43,10 @@ SX1509KeyTrigger = sx1509_ns.class_(
def check_keys(config: ConfigType) -> ConfigType:
for ch in config.get(CONF_KEYS, ""):
if not ch.isascii():
# Each key is reported as one byte, so only ASCII characters can be key codes
raise cv.Invalid(f"Key code {ch!r} is not an ASCII character")
if (
CONF_KEYS in config
and len(config[CONF_KEYS]) != config[CONF_KEY_ROWS] * config[CONF_KEY_COLUMNS]
@@ -98,8 +102,11 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_sleep_time(conf[CONF_SLEEP_TIME]))
cg.add(var.set_scan_time(conf[CONF_SCAN_TIME]))
cg.add(var.set_debounce_time(conf[CONF_DEBOUNCE_TIME]))
if keys := conf.get(CONF_KEYS):
cg.add(var.set_keys(keys))
if (keys := conf.get(CONF_KEYS)) is not None:
table = cg.shared_progmem_array(
"sx1509_keys", cg.uint8, list(keys.encode())
)
cg.add(var.set_keys(table))
for tconf in conf.get(CONF_ON_KEY, []):
trigger = cg.new_Pvariable(tconf[CONF_TRIGGER_ID])
cg.add(var.register_key_trigger(trigger))
+4 -2
View File
@@ -48,7 +48,7 @@ void SX1509Component::loop() {
uint16_t key_data = this->read_key_data();
for (auto *binary_sensor : this->keypad_binary_sensors_)
binary_sensor->process(key_data);
if (this->keys_.empty())
if (this->keys_ == nullptr)
return;
if (key_data == 0) {
this->last_key_ = 0;
@@ -64,7 +64,9 @@ void SX1509Component::loop() {
break;
}
col -= 8;
uint8_t key = this->keys_[row * this->cols_ + col];
if (row >= this->rows_ || col >= this->cols_) // a partial read can leave only a row or a column bit
return;
uint8_t key = progmem_read_byte(&this->keys_[row * this->cols_ + col]);
if (key == this->last_key_)
return;
this->last_key_ = key;
+3 -2
View File
@@ -51,7 +51,8 @@ class SX1509Component final : public Component,
this->cols_ = cols;
this->has_keypad_ = true;
};
void set_keys(std::string keys) { this->keys_ = std::move(keys); }; // NOLINT(performance-unnecessary-value-param)
/// `keys` is a codegen PROGMEM table with one key code per button.
void set_keys(const uint8_t *keys) { this->keys_ = keys; }
void set_sleep_time(uint16_t sleep_time) { this->sleep_time_ = sleep_time; };
void set_scan_time(uint8_t scan_time) { this->scan_time_ = scan_time; };
void set_debounce_time(uint8_t debounce_time = 1) { this->debounce_time_ = debounce_time; };
@@ -76,7 +77,7 @@ class SX1509Component final : public Component,
bool has_keypad_ = false;
uint8_t rows_ = 0;
uint8_t cols_ = 0;
std::string keys_;
const uint8_t *keys_{nullptr};
uint16_t sleep_time_ = 128;
uint8_t scan_time_ = 1;
uint8_t debounce_time_ = 1;
@@ -0,0 +1,23 @@
esphome:
name: test
esp8266:
board: esp01_1m
i2c:
sda: 4
scl: 5
sx1509:
- id: sx_a
address: 0x3E
keypad:
key_rows: 2
key_columns: 2
keys: "abcd"
- id: sx_b
address: 0x3F
keypad:
key_rows: 2
key_columns: 2
keys: "abcd"
+27
View File
@@ -0,0 +1,27 @@
"""SX1509 keypad key codes live in a shared PROGMEM table instead of a std::string."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components.sx1509 import check_keys
import esphome.config_validation as cv
def test_keys_use_shared_progmem_table(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("keys.yaml"))
assert (
"static constexpr uint8_t sx1509_keys[] PROGMEM = {97, 98, 99, 100};"
in main_cpp
)
assert main_cpp.count("->set_keys(sx1509_keys);") == 2
def test_non_ascii_keys_are_rejected() -> None:
with pytest.raises(cv.Invalid, match="'é' is not an ASCII"):
check_keys({"keys": "1é34"})