mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[display] SDL2 display driver for host platform (#6825)
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -455,7 +455,7 @@ jobs: | ||||
|         file: ${{ fromJson(needs.list-components.outputs.components) }} | ||||
|     steps: | ||||
|       - name: Install libsodium | ||||
|         run: sudo apt-get install libsodium-dev | ||||
|         run: sudo apt-get install libsodium-dev libsdl2-dev | ||||
|  | ||||
|       - name: Check out code from GitHub | ||||
|         uses: actions/checkout@v4.1.6 | ||||
|   | ||||
| @@ -320,6 +320,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/sdm_meter/* @jesserockz @polyfaces | ||||
| esphome/components/sdp3x/* @Azimath | ||||
| esphome/components/seeed_mr24hpc1/* @limengdu | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/sdl/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/sdl/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@clydebarrow"] | ||||
							
								
								
									
										72
									
								
								esphome/components/sdl/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								esphome/components/sdl/display.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| import subprocess | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import display | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_DIMENSIONS, | ||||
|     CONF_WIDTH, | ||||
|     CONF_HEIGHT, | ||||
|     CONF_LAMBDA, | ||||
|     PLATFORM_HOST, | ||||
| ) | ||||
|  | ||||
| sdl_ns = cg.esphome_ns.namespace("sdl") | ||||
| Sdl = sdl_ns.class_("Sdl", display.Display, cg.Component) | ||||
|  | ||||
|  | ||||
| CONF_SDL_OPTIONS = "sdl_options" | ||||
| CONF_SDL_ID = "sdl_id" | ||||
|  | ||||
|  | ||||
| def get_sdl_options(value): | ||||
|     if value != "": | ||||
|         return value | ||||
|     try: | ||||
|         return subprocess.check_output(["sdl2-config", "--cflags", "--libs"]).decode() | ||||
|     except Exception as e: | ||||
|         raise cv.Invalid("Unable to run sdl2-config - have you installed sdl2?") from e | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     display.FULL_DISPLAY_SCHEMA.extend( | ||||
|         cv.Schema( | ||||
|             { | ||||
|                 cv.GenerateID(): cv.declare_id(Sdl), | ||||
|                 cv.Optional(CONF_SDL_OPTIONS, default=""): get_sdl_options, | ||||
|                 cv.Required(CONF_DIMENSIONS): cv.Any( | ||||
|                     cv.dimensions, | ||||
|                     cv.Schema( | ||||
|                         { | ||||
|                             cv.Required(CONF_WIDTH): cv.int_, | ||||
|                             cv.Required(CONF_HEIGHT): cv.int_, | ||||
|                         } | ||||
|                     ), | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|     ), | ||||
|     cv.only_on(PLATFORM_HOST), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     for option in config[CONF_SDL_OPTIONS].split(): | ||||
|         cg.add_build_flag(option) | ||||
|     cg.add_build_flag("-DSDL_BYTEORDER=4321") | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await display.register_display(var, config) | ||||
|  | ||||
|     dimensions = config[CONF_DIMENSIONS] | ||||
|     if isinstance(dimensions, dict): | ||||
|         cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT])) | ||||
|     else: | ||||
|         (width, height) = dimensions | ||||
|         cg.add(var.set_dimensions(width, height)) | ||||
|  | ||||
|     if lamb := config.get(CONF_LAMBDA): | ||||
|         lambda_ = await cg.process_lambda( | ||||
|             lamb, [(display.DisplayRef, "it")], return_type=cg.void | ||||
|         ) | ||||
|         cg.add(var.set_writer(lambda_)) | ||||
							
								
								
									
										96
									
								
								esphome/components/sdl/sdl_esphome.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								esphome/components/sdl/sdl_esphome.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| #ifdef USE_HOST | ||||
| #include "sdl_esphome.h" | ||||
| #include "esphome/components/display/display_color_utils.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sdl { | ||||
|  | ||||
| void Sdl::setup() { | ||||
|   ESP_LOGD(TAG, "Starting setup"); | ||||
|   SDL_Init(SDL_INIT_VIDEO); | ||||
|   this->window_ = SDL_CreateWindow(App.get_name().c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, | ||||
|                                    this->width_, this->height_, 0); | ||||
|   this->renderer_ = SDL_CreateRenderer(this->window_, -1, SDL_RENDERER_SOFTWARE); | ||||
|   this->texture_ = | ||||
|       SDL_CreateTexture(this->renderer_, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STATIC, this->width_, this->height_); | ||||
|   SDL_SetTextureBlendMode(this->texture_, SDL_BLENDMODE_BLEND); | ||||
|   ESP_LOGD(TAG, "Setup Complete"); | ||||
| } | ||||
| void Sdl::update() { | ||||
|   this->do_update_(); | ||||
|   if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) | ||||
|     return; | ||||
|   SDL_Rect rect{this->x_low_, this->y_low_, this->x_high_ + 1 - this->x_low_, this->y_high_ + 1 - this->y_low_}; | ||||
|   this->x_low_ = this->width_; | ||||
|   this->y_low_ = this->height_; | ||||
|   this->x_high_ = 0; | ||||
|   this->y_high_ = 0; | ||||
|   SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect); | ||||
|   SDL_RenderPresent(this->renderer_); | ||||
| } | ||||
|  | ||||
| void Sdl::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) { | ||||
|   SDL_Rect rect{x_start, y_start, w, h}; | ||||
|   if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || big_endian) { | ||||
|     display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, | ||||
|                                      x_pad); | ||||
|   } else { | ||||
|     auto stride = x_offset + w + x_pad; | ||||
|     auto data = ptr + (stride * y_offset + x_offset) * 2; | ||||
|     SDL_UpdateTexture(this->texture_, &rect, data, stride * 2); | ||||
|   } | ||||
|   SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect); | ||||
|   SDL_RenderPresent(this->renderer_); | ||||
| } | ||||
|  | ||||
| void Sdl::draw_pixel_at(int x, int y, Color color) { | ||||
|   SDL_Rect rect{x, y, 1, 1}; | ||||
|   auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB)); | ||||
|   SDL_UpdateTexture(this->texture_, &rect, &data, 2); | ||||
|   if (x < this->x_low_) | ||||
|     this->x_low_ = x; | ||||
|   if (y < this->y_low_) | ||||
|     this->y_low_ = y; | ||||
|   if (x > this->x_high_) | ||||
|     this->x_high_ = x; | ||||
|   if (y > this->y_high_) | ||||
|     this->y_high_ = y; | ||||
| } | ||||
|  | ||||
| void Sdl::loop() { | ||||
|   SDL_Event e; | ||||
|   if (SDL_PollEvent(&e)) { | ||||
|     switch (e.type) { | ||||
|       case SDL_QUIT: | ||||
|         exit(0); | ||||
|  | ||||
|       case SDL_MOUSEBUTTONDOWN: | ||||
|       case SDL_MOUSEBUTTONUP: | ||||
|         if (e.button.button == 1) { | ||||
|           this->mouse_x = e.button.x; | ||||
|           this->mouse_y = e.button.y; | ||||
|           this->mouse_down = e.button.state != 0; | ||||
|         } | ||||
|         break; | ||||
|  | ||||
|       case SDL_MOUSEMOTION: | ||||
|         if (e.motion.state & 1) { | ||||
|           this->mouse_x = e.button.x; | ||||
|           this->mouse_y = e.button.y; | ||||
|           this->mouse_down = true; | ||||
|         } else { | ||||
|           this->mouse_down = false; | ||||
|         } | ||||
|         break; | ||||
|  | ||||
|       default: | ||||
|         ESP_LOGV(TAG, "Event %d", e.type); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace sdl | ||||
| }  // namespace esphome | ||||
| #endif | ||||
							
								
								
									
										54
									
								
								esphome/components/sdl/sdl_esphome.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								esphome/components/sdl/sdl_esphome.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_HOST | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/components/display/display.h" | ||||
| #define SDL_MAIN_HANDLED | ||||
| #include "SDL.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sdl { | ||||
|  | ||||
| constexpr static const char *const TAG = "sdl"; | ||||
|  | ||||
| class Sdl : public display::Display { | ||||
|  public: | ||||
|   display::DisplayType get_display_type() override { return display::DISPLAY_TYPE_COLOR; } | ||||
|   void update() override; | ||||
|   void loop() override; | ||||
|   void setup() override; | ||||
|   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 set_dimensions(uint16_t width, uint16_t height) { | ||||
|     this->width_ = width; | ||||
|     this->height_ = height; | ||||
|   } | ||||
|   int get_width() override { return this->width_; } | ||||
|   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); } | ||||
|  | ||||
|   int mouse_x{}; | ||||
|   int mouse_y{}; | ||||
|   bool mouse_down{}; | ||||
|  | ||||
|  protected: | ||||
|   int get_width_internal() override { return this->width_; } | ||||
|   int get_height_internal() override { return this->height_; } | ||||
|   int width_{}; | ||||
|   int height_{}; | ||||
|   SDL_Renderer *renderer_{}; | ||||
|   SDL_Window *window_{}; | ||||
|   SDL_Texture *texture_{}; | ||||
|   uint16_t x_low_{0}; | ||||
|   uint16_t y_low_{0}; | ||||
|   uint16_t x_high_{0}; | ||||
|   uint16_t y_high_{0}; | ||||
| }; | ||||
| }  // namespace sdl | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										22
									
								
								esphome/components/sdl/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/sdl/touchscreen/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| from esphome.components import touchscreen | ||||
| from ..display import Sdl, sdl_ns, CONF_SDL_ID | ||||
|  | ||||
| SdlTouchscreen = sdl_ns.class_("SdlTouchscreen", touchscreen.Touchscreen) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(SdlTouchscreen), | ||||
|         cv.GenerateID(CONF_SDL_ID): cv.use_id(Sdl), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_parented(var, config[CONF_SDL_ID]) | ||||
|     await touchscreen.register_touchscreen(var, config) | ||||
							
								
								
									
										26
									
								
								esphome/components/sdl/touchscreen/sdl_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/sdl/touchscreen/sdl_touchscreen.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_HOST | ||||
| #include "../sdl_esphome.h" | ||||
| #include "esphome/components/touchscreen/touchscreen.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sdl { | ||||
|  | ||||
| class SdlTouchscreen : public touchscreen::Touchscreen, public Parented<Sdl> { | ||||
|  public: | ||||
|   void setup() override { | ||||
|     this->x_raw_max_ = this->display_->get_width(); | ||||
|     this->y_raw_max_ = this->display_->get_height(); | ||||
|   } | ||||
|  | ||||
|   void update_touches() override { | ||||
|     if (this->parent_->mouse_down) { | ||||
|       add_raw_touch_position_(0, this->parent_->mouse_x, this->parent_->mouse_y); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace sdl | ||||
| }  // namespace esphome | ||||
| #endif | ||||
							
								
								
									
										12
									
								
								tests/components/sdl/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/components/sdl/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| host: | ||||
|   mac_address: "62:23:45:AF:B3:DD" | ||||
|  | ||||
| display: | ||||
|   - platform: sdl | ||||
|     id: sdl_display | ||||
|     update_interval: 1s | ||||
|     auto_clear_enabled: false | ||||
|     show_test_card: true | ||||
|     dimensions: | ||||
|       width: 450 | ||||
|       height: 600 | ||||
							
								
								
									
										1
									
								
								tests/components/sdl/test.host.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/sdl/test.host.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <<: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user