From 361de223707158e15e6b3303051171c58b044216 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 26 May 2025 22:16:27 -0700 Subject: [PATCH] [sx1509] add support for keys (#8413) Co-authored-by: Samuel Sieb Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/sx1509/__init__.py | 73 +++++++++++++++++++-------- esphome/components/sx1509/sx1509.cpp | 30 +++++++++-- esphome/components/sx1509/sx1509.h | 10 +++- tests/components/sx1509/common.yaml | 11 ++++ 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index b1702b5ade..f1b08a505a 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -1,6 +1,6 @@ -from esphome import pins +from esphome import automation, pins import esphome.codegen as cg -from esphome.components import i2c +from esphome.components import i2c, key_provider import esphome.config_validation as cv from esphome.const import ( CONF_ID, @@ -8,13 +8,16 @@ from esphome.const import ( CONF_INVERTED, CONF_MODE, CONF_NUMBER, + CONF_ON_KEY, CONF_OPEN_DRAIN, CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, + CONF_TRIGGER_ID, ) CONF_KEYPAD = "keypad" +CONF_KEYS = "keys" CONF_KEY_ROWS = "key_rows" CONF_KEY_COLUMNS = "key_columns" CONF_SLEEP_TIME = "sleep_time" @@ -22,22 +25,47 @@ CONF_SCAN_TIME = "scan_time" CONF_DEBOUNCE_TIME = "debounce_time" CONF_SX1509_ID = "sx1509_id" +AUTO_LOAD = ["key_provider"] DEPENDENCIES = ["i2c"] MULTI_CONF = True sx1509_ns = cg.esphome_ns.namespace("sx1509") -SX1509Component = sx1509_ns.class_("SX1509Component", cg.Component, i2c.I2CDevice) +SX1509Component = sx1509_ns.class_( + "SX1509Component", cg.Component, i2c.I2CDevice, key_provider.KeyProvider +) SX1509GPIOPin = sx1509_ns.class_("SX1509GPIOPin", cg.GPIOPin) +SX1509KeyTrigger = sx1509_ns.class_( + "SX1509KeyTrigger", automation.Trigger.template(cg.uint8) +) -KEYPAD_SCHEMA = cv.Schema( - { - cv.Required(CONF_KEY_ROWS): cv.int_range(min=1, max=8), - cv.Required(CONF_KEY_COLUMNS): cv.int_range(min=1, max=8), - cv.Optional(CONF_SLEEP_TIME): cv.int_range(min=128, max=8192), - cv.Optional(CONF_SCAN_TIME): cv.int_range(min=1, max=128), - cv.Optional(CONF_DEBOUNCE_TIME): cv.int_range(min=1, max=64), - } + +def check_keys(config): + if CONF_KEYS in config: + if len(config[CONF_KEYS]) != config[CONF_KEY_ROWS] * config[CONF_KEY_COLUMNS]: + raise cv.Invalid( + "The number of key codes must equal the number of rows * columns" + ) + return config + + +KEYPAD_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_KEY_ROWS): cv.int_range(min=2, max=8), + cv.Required(CONF_KEY_COLUMNS): cv.int_range(min=1, max=8), + cv.Optional(CONF_SLEEP_TIME): cv.int_range(min=128, max=8192), + cv.Optional(CONF_SCAN_TIME): cv.int_range(min=1, max=128), + cv.Optional(CONF_DEBOUNCE_TIME): cv.int_range(min=1, max=64), + cv.Optional(CONF_KEYS): cv.string, + cv.Optional(CONF_ON_KEY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SX1509KeyTrigger), + } + ), + } + ), + check_keys, ) CONFIG_SCHEMA = ( @@ -56,17 +84,22 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - if CONF_KEYPAD in config: - keypad = config[CONF_KEYPAD] - cg.add(var.set_rows_cols(keypad[CONF_KEY_ROWS], keypad[CONF_KEY_COLUMNS])) + if conf := config.get(CONF_KEYPAD): + cg.add(var.set_rows_cols(conf[CONF_KEY_ROWS], conf[CONF_KEY_COLUMNS])) if ( - CONF_SLEEP_TIME in keypad - and CONF_SCAN_TIME in keypad - and CONF_DEBOUNCE_TIME in keypad + CONF_SLEEP_TIME in conf + and CONF_SCAN_TIME in conf + and CONF_DEBOUNCE_TIME in conf ): - cg.add(var.set_sleep_time(keypad[CONF_SLEEP_TIME])) - cg.add(var.set_scan_time(keypad[CONF_SCAN_TIME])) - cg.add(var.set_debounce_time(keypad[CONF_DEBOUNCE_TIME])) + 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)) + for tconf in conf.get(CONF_ON_KEY, []): + trigger = cg.new_Pvariable(tconf[CONF_TRIGGER_ID]) + cg.add(var.register_key_trigger(trigger)) + await automation.build_automation(trigger, [(cg.uint8, "x")], tconf) def validate_mode(value): diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 855a90bacd..a4808c86e2 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -48,6 +48,30 @@ 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()) + return; + if (key_data == 0) { + this->last_key_ = 0; + return; + } + int row, col; + for (row = 0; row < 7; row++) { + if (key_data & (1 << row)) + break; + } + for (col = 8; col < 15; col++) { + if (key_data & (1 << col)) + break; + } + col -= 8; + uint8_t key = this->keys_[row * this->cols_ + col]; + if (key == this->last_key_) + return; + this->last_key_ = key; + ESP_LOGV(TAG, "row %d, col %d, key '%c'", row, col, key); + for (auto &trigger : this->key_triggers_) + trigger->trigger(key); + this->send_key_(key); } } @@ -230,9 +254,9 @@ void SX1509Component::setup_keypad_() { scan_time_bits &= 0b111; // Scan time is bits 2:0 temp_byte = sleep_time_ | scan_time_bits; this->write_byte(REG_KEY_CONFIG_1, temp_byte); - rows_ = (rows_ - 1) & 0b111; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc. - cols_ = (cols_ - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc. - this->write_byte(REG_KEY_CONFIG_2, (rows_ << 3) | cols_); + temp_byte = ((this->rows_ - 1) & 0b111) << 3; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc. + temp_byte |= (this->cols_ - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc. + this->write_byte(REG_KEY_CONFIG_2, temp_byte); } uint16_t SX1509Component::read_key_data() { diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index 9e4f31aab0..c0e86aa8a1 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/i2c/i2c.h" +#include "esphome/components/key_provider/key_provider.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "sx1509_gpio_pin.h" @@ -27,7 +28,9 @@ class SX1509Processor { virtual void process(uint16_t data){}; }; -class SX1509Component : public Component, public i2c::I2CDevice { +class SX1509KeyTrigger : public Trigger {}; + +class SX1509Component : public Component, public i2c::I2CDevice, public key_provider::KeyProvider { public: SX1509Component() = default; @@ -47,12 +50,14 @@ class SX1509Component : public Component, public i2c::I2CDevice { this->cols_ = cols; this->has_keypad_ = true; }; + void set_keys(std::string keys) { this->keys_ = std::move(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; }; void register_keypad_binary_sensor(SX1509Processor *binary_sensor) { this->keypad_binary_sensors_.push_back(binary_sensor); } + void register_key_trigger(SX1509KeyTrigger *trig) { this->key_triggers_.push_back(trig); }; void setup_led_driver(uint8_t pin); protected: @@ -65,10 +70,13 @@ class SX1509Component : public Component, public i2c::I2CDevice { bool has_keypad_ = false; uint8_t rows_ = 0; uint8_t cols_ = 0; + std::string keys_; uint16_t sleep_time_ = 128; uint8_t scan_time_ = 1; uint8_t debounce_time_ = 1; + uint8_t last_key_ = 0; std::vector keypad_binary_sensors_; + std::vector key_triggers_; uint32_t last_loop_timestamp_ = 0; const uint32_t min_loop_period_ = 15; // ms diff --git a/tests/components/sx1509/common.yaml b/tests/components/sx1509/common.yaml index a09d850649..a83217e579 100644 --- a/tests/components/sx1509/common.yaml +++ b/tests/components/sx1509/common.yaml @@ -6,6 +6,12 @@ i2c: sx1509: - id: sx1509_hub address: 0x3E + keypad: + key_rows: 2 + key_columns: 2 + keys: abcd + on_key: + - lambda: ESP_LOGD("test", "got key '%c'", x); binary_sensor: - platform: gpio @@ -13,6 +19,11 @@ binary_sensor: pin: sx1509: sx1509_hub number: 3 + - platform: sx1509 + sx1509_id: sx1509_hub + name: "keypadkey_0" + row: 0 + col: 0 switch: - platform: gpio