From e337bd7beb41d28c8d06026cb18f37c725e684dd Mon Sep 17 00:00:00 2001 From: bdm310 Date: Wed, 5 Feb 2025 02:53:23 -0800 Subject: [PATCH] [sdl] Implement binary sensors from keystrokes (#8207) Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- CODEOWNERS | 2 +- esphome/components/sdl/binary_sensor.py | 270 ++++++++++++++++++++++++ esphome/components/sdl/sdl_esphome.cpp | 16 ++ esphome/components/sdl/sdl_esphome.h | 9 + tests/components/sdl/common.yaml | 11 + 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 esphome/components/sdl/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 03e26bcb84..eab02efffb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -357,7 +357,7 @@ esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti esphome/components/scd4x/* @martgras @sjtrny esphome/components/script/* @esphome/core -esphome/components/sdl/* @clydebarrow +esphome/components/sdl/* @bdm310 @clydebarrow esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/seeed_mr24hpc1/* @limengdu diff --git a/esphome/components/sdl/binary_sensor.py b/esphome/components/sdl/binary_sensor.py new file mode 100644 index 0000000000..3ea6c2d218 --- /dev/null +++ b/esphome/components/sdl/binary_sensor.py @@ -0,0 +1,270 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +from esphome.components.binary_sensor import BinarySensor +import esphome.config_validation as cv +from esphome.const import CONF_KEY +from esphome.core import Lambda +from esphome.cpp_generator import ExpressionStatement, RawExpression + +from .display import CONF_SDL_ID, Sdl + +CODEOWNERS = ["@bdm310"] + +STATE_ARG = "state" + +SDL_KEYMAP = { + "SDLK_UNKNOWN": 0, + "SDLK_FIRST": 0, + "SDLK_BACKSPACE": 8, + "SDLK_TAB": 9, + "SDLK_CLEAR": 12, + "SDLK_RETURN": 13, + "SDLK_PAUSE": 19, + "SDLK_ESCAPE": 27, + "SDLK_SPACE": 32, + "SDLK_EXCLAIM": 33, + "SDLK_QUOTEDBL": 34, + "SDLK_HASH": 35, + "SDLK_DOLLAR": 36, + "SDLK_AMPERSAND": 38, + "SDLK_QUOTE": 39, + "SDLK_LEFTPAREN": 40, + "SDLK_RIGHTPAREN": 41, + "SDLK_ASTERISK": 42, + "SDLK_PLUS": 43, + "SDLK_COMMA": 44, + "SDLK_MINUS": 45, + "SDLK_PERIOD": 46, + "SDLK_SLASH": 47, + "SDLK_0": 48, + "SDLK_1": 49, + "SDLK_2": 50, + "SDLK_3": 51, + "SDLK_4": 52, + "SDLK_5": 53, + "SDLK_6": 54, + "SDLK_7": 55, + "SDLK_8": 56, + "SDLK_9": 57, + "SDLK_COLON": 58, + "SDLK_SEMICOLON": 59, + "SDLK_LESS": 60, + "SDLK_EQUALS": 61, + "SDLK_GREATER": 62, + "SDLK_QUESTION": 63, + "SDLK_AT": 64, + "SDLK_LEFTBRACKET": 91, + "SDLK_BACKSLASH": 92, + "SDLK_RIGHTBRACKET": 93, + "SDLK_CARET": 94, + "SDLK_UNDERSCORE": 95, + "SDLK_BACKQUOTE": 96, + "SDLK_a": 97, + "SDLK_b": 98, + "SDLK_c": 99, + "SDLK_d": 100, + "SDLK_e": 101, + "SDLK_f": 102, + "SDLK_g": 103, + "SDLK_h": 104, + "SDLK_i": 105, + "SDLK_j": 106, + "SDLK_k": 107, + "SDLK_l": 108, + "SDLK_m": 109, + "SDLK_n": 110, + "SDLK_o": 111, + "SDLK_p": 112, + "SDLK_q": 113, + "SDLK_r": 114, + "SDLK_s": 115, + "SDLK_t": 116, + "SDLK_u": 117, + "SDLK_v": 118, + "SDLK_w": 119, + "SDLK_x": 120, + "SDLK_y": 121, + "SDLK_z": 122, + "SDLK_DELETE": 127, + "SDLK_WORLD_0": 160, + "SDLK_WORLD_1": 161, + "SDLK_WORLD_2": 162, + "SDLK_WORLD_3": 163, + "SDLK_WORLD_4": 164, + "SDLK_WORLD_5": 165, + "SDLK_WORLD_6": 166, + "SDLK_WORLD_7": 167, + "SDLK_WORLD_8": 168, + "SDLK_WORLD_9": 169, + "SDLK_WORLD_10": 170, + "SDLK_WORLD_11": 171, + "SDLK_WORLD_12": 172, + "SDLK_WORLD_13": 173, + "SDLK_WORLD_14": 174, + "SDLK_WORLD_15": 175, + "SDLK_WORLD_16": 176, + "SDLK_WORLD_17": 177, + "SDLK_WORLD_18": 178, + "SDLK_WORLD_19": 179, + "SDLK_WORLD_20": 180, + "SDLK_WORLD_21": 181, + "SDLK_WORLD_22": 182, + "SDLK_WORLD_23": 183, + "SDLK_WORLD_24": 184, + "SDLK_WORLD_25": 185, + "SDLK_WORLD_26": 186, + "SDLK_WORLD_27": 187, + "SDLK_WORLD_28": 188, + "SDLK_WORLD_29": 189, + "SDLK_WORLD_30": 190, + "SDLK_WORLD_31": 191, + "SDLK_WORLD_32": 192, + "SDLK_WORLD_33": 193, + "SDLK_WORLD_34": 194, + "SDLK_WORLD_35": 195, + "SDLK_WORLD_36": 196, + "SDLK_WORLD_37": 197, + "SDLK_WORLD_38": 198, + "SDLK_WORLD_39": 199, + "SDLK_WORLD_40": 200, + "SDLK_WORLD_41": 201, + "SDLK_WORLD_42": 202, + "SDLK_WORLD_43": 203, + "SDLK_WORLD_44": 204, + "SDLK_WORLD_45": 205, + "SDLK_WORLD_46": 206, + "SDLK_WORLD_47": 207, + "SDLK_WORLD_48": 208, + "SDLK_WORLD_49": 209, + "SDLK_WORLD_50": 210, + "SDLK_WORLD_51": 211, + "SDLK_WORLD_52": 212, + "SDLK_WORLD_53": 213, + "SDLK_WORLD_54": 214, + "SDLK_WORLD_55": 215, + "SDLK_WORLD_56": 216, + "SDLK_WORLD_57": 217, + "SDLK_WORLD_58": 218, + "SDLK_WORLD_59": 219, + "SDLK_WORLD_60": 220, + "SDLK_WORLD_61": 221, + "SDLK_WORLD_62": 222, + "SDLK_WORLD_63": 223, + "SDLK_WORLD_64": 224, + "SDLK_WORLD_65": 225, + "SDLK_WORLD_66": 226, + "SDLK_WORLD_67": 227, + "SDLK_WORLD_68": 228, + "SDLK_WORLD_69": 229, + "SDLK_WORLD_70": 230, + "SDLK_WORLD_71": 231, + "SDLK_WORLD_72": 232, + "SDLK_WORLD_73": 233, + "SDLK_WORLD_74": 234, + "SDLK_WORLD_75": 235, + "SDLK_WORLD_76": 236, + "SDLK_WORLD_77": 237, + "SDLK_WORLD_78": 238, + "SDLK_WORLD_79": 239, + "SDLK_WORLD_80": 240, + "SDLK_WORLD_81": 241, + "SDLK_WORLD_82": 242, + "SDLK_WORLD_83": 243, + "SDLK_WORLD_84": 244, + "SDLK_WORLD_85": 245, + "SDLK_WORLD_86": 246, + "SDLK_WORLD_87": 247, + "SDLK_WORLD_88": 248, + "SDLK_WORLD_89": 249, + "SDLK_WORLD_90": 250, + "SDLK_WORLD_91": 251, + "SDLK_WORLD_92": 252, + "SDLK_WORLD_93": 253, + "SDLK_WORLD_94": 254, + "SDLK_WORLD_95": 255, + "SDLK_KP0": 256, + "SDLK_KP1": 257, + "SDLK_KP2": 258, + "SDLK_KP3": 259, + "SDLK_KP4": 260, + "SDLK_KP5": 261, + "SDLK_KP6": 262, + "SDLK_KP7": 263, + "SDLK_KP8": 264, + "SDLK_KP9": 265, + "SDLK_KP_PERIOD": 266, + "SDLK_KP_DIVIDE": 267, + "SDLK_KP_MULTIPLY": 268, + "SDLK_KP_MINUS": 269, + "SDLK_KP_PLUS": 270, + "SDLK_KP_ENTER": 271, + "SDLK_KP_EQUALS": 272, + "SDLK_UP": 273, + "SDLK_DOWN": 274, + "SDLK_RIGHT": 275, + "SDLK_LEFT": 276, + "SDLK_INSERT": 277, + "SDLK_HOME": 278, + "SDLK_END": 279, + "SDLK_PAGEUP": 280, + "SDLK_PAGEDOWN": 281, + "SDLK_F1": 282, + "SDLK_F2": 283, + "SDLK_F3": 284, + "SDLK_F4": 285, + "SDLK_F5": 286, + "SDLK_F6": 287, + "SDLK_F7": 288, + "SDLK_F8": 289, + "SDLK_F9": 290, + "SDLK_F10": 291, + "SDLK_F11": 292, + "SDLK_F12": 293, + "SDLK_F13": 294, + "SDLK_F14": 295, + "SDLK_F15": 296, + "SDLK_NUMLOCK": 300, + "SDLK_CAPSLOCK": 301, + "SDLK_SCROLLOCK": 302, + "SDLK_RSHIFT": 303, + "SDLK_LSHIFT": 304, + "SDLK_RCTRL": 305, + "SDLK_LCTRL": 306, + "SDLK_RALT": 307, + "SDLK_LALT": 308, + "SDLK_RMETA": 309, + "SDLK_LMETA": 310, + "SDLK_LSUPER": 311, + "SDLK_RSUPER": 312, + "SDLK_MODE": 313, + "SDLK_COMPOSE": 314, + "SDLK_HELP": 315, + "SDLK_PRINT": 316, + "SDLK_SYSREQ": 317, + "SDLK_BREAK": 318, + "SDLK_MENU": 319, + "SDLK_POWER": 320, + "SDLK_EURO": 321, + "SDLK_UNDO": 322, +} + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(BinarySensor) + .extend( + { + cv.Required(CONF_KEY): cv.enum(SDL_KEYMAP), + cv.GenerateID(CONF_SDL_ID): cv.use_id(Sdl), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + parent = await cg.get_variable(config[CONF_SDL_ID]) + listener = Lambda( + str(ExpressionStatement(var.publish_state(RawExpression(STATE_ARG)))) + ) + listener = await cg.process_lambda(listener, [(cg.bool_, STATE_ARG)]) + cg.add(parent.add_key_listener(config[CONF_KEY], listener)) diff --git a/esphome/components/sdl/sdl_esphome.cpp b/esphome/components/sdl/sdl_esphome.cpp index 8f0821a2fa..42dfe687e9 100644 --- a/esphome/components/sdl/sdl_esphome.cpp +++ b/esphome/components/sdl/sdl_esphome.cpp @@ -61,6 +61,12 @@ void Sdl::draw_pixel_at(int x, int y, Color color) { this->y_high_ = y; } +void Sdl::process_key(uint32_t keycode, bool down) { + auto callback = this->key_callbacks_.find(keycode); + if (callback != this->key_callbacks_.end()) + callback->second(down); +} + void Sdl::loop() { SDL_Event e; if (SDL_PollEvent(&e)) { @@ -87,6 +93,16 @@ void Sdl::loop() { } break; + case SDL_KEYDOWN: + ESP_LOGD(TAG, "keydown %d", e.key.keysym.sym); + this->process_key(e.key.keysym.sym, true); + break; + + case SDL_KEYUP: + ESP_LOGD(TAG, "keyup %d", e.key.keysym.sym); + this->process_key(e.key.keysym.sym, false); + break; + case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: diff --git a/esphome/components/sdl/sdl_esphome.h b/esphome/components/sdl/sdl_esphome.h index 4b0e59c9fe..39ea3ed417 100644 --- a/esphome/components/sdl/sdl_esphome.h +++ b/esphome/components/sdl/sdl_esphome.h @@ -7,6 +7,7 @@ #include "esphome/components/display/display.h" #define SDL_MAIN_HANDLED #include "SDL.h" +#include namespace esphome { namespace sdl { @@ -22,6 +23,7 @@ class Sdl : public display::Display { void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; + void process_key(uint32_t keycode, bool down); void set_dimensions(uint16_t width, uint16_t height) { this->width_ = width; this->height_ = height; @@ -30,6 +32,12 @@ class Sdl : public display::Display { int get_height() override { return this->height_; } float get_setup_priority() const override { return setup_priority::HARDWARE; } void dump_config() override { LOG_DISPLAY("", "SDL", this); } + void add_key_listener(int32_t keycode, std::function &&callback) { + if (!this->key_callbacks_.count(keycode)) { + this->key_callbacks_[keycode] = CallbackManager(); + } + this->key_callbacks_[keycode].add(std::move(callback)); + } int mouse_x{}; int mouse_y{}; @@ -48,6 +56,7 @@ class Sdl : public display::Display { uint16_t y_low_{0}; uint16_t x_high_{0}; uint16_t y_high_{0}; + std::map> key_callbacks_{}; }; } // namespace sdl } // namespace esphome diff --git a/tests/components/sdl/common.yaml b/tests/components/sdl/common.yaml index 0192f054b5..50fa4a5990 100644 --- a/tests/components/sdl/common.yaml +++ b/tests/components/sdl/common.yaml @@ -10,3 +10,14 @@ display: dimensions: width: 450 height: 600 + +binary_sensor: + - platform: sdl + id: key_up + key: SDLK_a + - platform: sdl + id: key_down + key: SDLK_d + - platform: sdl + id: key_enter + key: SDLK_s