mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	[sx1509] add support for keys (#8413)
Co-authored-by: Samuel Sieb <samuel@sieb.net> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -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): | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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<uint8_t> {}; | ||||
|  | ||||
| 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<SX1509Processor *> keypad_binary_sensors_; | ||||
|   std::vector<SX1509KeyTrigger *> key_triggers_; | ||||
|  | ||||
|   uint32_t last_loop_timestamp_ = 0; | ||||
|   const uint32_t min_loop_period_ = 15;  // ms | ||||
|   | ||||
		Reference in New Issue
	
	Block a user