mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'optional_api' into integration
This commit is contained in:
		| @@ -243,21 +243,7 @@ void APIConnection::loop() { | ||||
| #endif | ||||
|  | ||||
|   if (state_subs_at_ >= 0) { | ||||
|     const auto &subs = this->parent_->get_state_subs(); | ||||
|     if (state_subs_at_ < static_cast<int>(subs.size())) { | ||||
|       auto &it = subs[state_subs_at_]; | ||||
|       SubscribeHomeAssistantStateResponse resp; | ||||
|       resp.set_entity_id(StringRef(it.entity_id)); | ||||
|       // attribute.value() returns temporary - must store it | ||||
|       std::string attribute_value = it.attribute.value(); | ||||
|       resp.set_attribute(StringRef(attribute_value)); | ||||
|       resp.once = it.once; | ||||
|       if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { | ||||
|         state_subs_at_++; | ||||
|       } | ||||
|     } else { | ||||
|       state_subs_at_ = -1; | ||||
|     } | ||||
|     this->process_state_subscriptions_(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -642,17 +628,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection | ||||
|   if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) | ||||
|     resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value()); | ||||
|   if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) { | ||||
|     // custom_fan_mode.value() returns temporary - must store it | ||||
|     std::string custom_fan_mode = climate->custom_fan_mode.value(); | ||||
|     resp.set_custom_fan_mode(StringRef(custom_fan_mode)); | ||||
|     resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value())); | ||||
|   } | ||||
|   if (traits.get_supports_presets() && climate->preset.has_value()) { | ||||
|     resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); | ||||
|   } | ||||
|   if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) { | ||||
|     // custom_preset.value() returns temporary - must store it | ||||
|     std::string custom_preset = climate->custom_preset.value(); | ||||
|     resp.set_custom_preset(StringRef(custom_preset)); | ||||
|     resp.set_custom_preset(StringRef(climate->custom_preset.value())); | ||||
|   } | ||||
|   if (traits.get_supports_swing_modes()) | ||||
|     resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode); | ||||
| @@ -1836,5 +1818,27 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection | ||||
|   return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
|  | ||||
| void APIConnection::process_state_subscriptions_() { | ||||
|   const auto &subs = this->parent_->get_state_subs(); | ||||
|   if (this->state_subs_at_ >= static_cast<int>(subs.size())) { | ||||
|     this->state_subs_at_ = -1; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const auto &it = subs[this->state_subs_at_]; | ||||
|   SubscribeHomeAssistantStateResponse resp; | ||||
|   resp.set_entity_id(StringRef(it.entity_id)); | ||||
|  | ||||
|   // Avoid string copy by directly using the optional's value if it exists | ||||
|   if (it.attribute.has_value()) { | ||||
|     resp.set_attribute(StringRef(it.attribute.value())); | ||||
|   } | ||||
|  | ||||
|   resp.once = it.once; | ||||
|   if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { | ||||
|     this->state_subs_at_++; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace esphome::api | ||||
| #endif | ||||
|   | ||||
| @@ -288,6 +288,9 @@ class APIConnection : public APIServerConnection { | ||||
|   // Helper function to handle authentication completion | ||||
|   void complete_authentication_(); | ||||
|  | ||||
|   // Process state subscriptions efficiently | ||||
|   void process_state_subscriptions_(); | ||||
|  | ||||
|   // Non-template helper to encode any ProtoMessage | ||||
|   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, | ||||
|                                            uint32_t remaining_size, bool is_single); | ||||
|   | ||||
| @@ -35,11 +35,10 @@ namespace esphome::api { | ||||
|  * | ||||
|  * Unsafe Patterns (WILL cause crashes/corruption): | ||||
|  * 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value | ||||
|  * 2. Optional values: msg.set_field(StringRef(optional.value())) // value() returns a copy | ||||
|  * 3. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary | ||||
|  * 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary | ||||
|  * | ||||
|  * For unsafe patterns, store in a local variable first: | ||||
|  *    std::string temp = optional.value();  // or get_string() or str1 + str2 | ||||
|  *    std::string temp = get_string();  // or str1 + str2 | ||||
|  *    msg.set_field(StringRef(temp)); | ||||
|  * | ||||
|  * The send_*_response pattern ensures proper lifetime management by encoding | ||||
|   | ||||
| @@ -230,7 +230,7 @@ class DriverChip: | ||||
|     ): | ||||
|         name = name.upper() | ||||
|         self.name = name | ||||
|         self.initsequence = initsequence | ||||
|         self.initsequence = initsequence or defaults.get("init_sequence") | ||||
|         self.defaults = defaults | ||||
|         DriverChip.models[name] = self | ||||
|  | ||||
| @@ -347,7 +347,7 @@ class DriverChip: | ||||
|         Pixel format, color order, and orientation will be set. | ||||
|         Returns a tuple of the init sequence and the computed MADCTL value. | ||||
|         """ | ||||
|         sequence = list(self.initsequence) | ||||
|         sequence = list(self.initsequence or ()) | ||||
|         custom_sequence = config.get(CONF_INIT_SEQUENCE, []) | ||||
|         sequence.extend(custom_sequence) | ||||
|         # Ensure each command is a tuple | ||||
| @@ -356,6 +356,8 @@ class DriverChip: | ||||
|         # Set pixel format if not already in the custom sequence | ||||
|         pixel_mode = config[CONF_PIXEL_MODE] | ||||
|         if not isinstance(pixel_mode, int): | ||||
|             if not pixel_mode.endswith("bit"): | ||||
|                 pixel_mode = f"{pixel_mode}bit" | ||||
|             pixel_mode = PIXEL_MODES[pixel_mode] | ||||
|         sequence.append((PIXFMT, pixel_mode)) | ||||
|  | ||||
|   | ||||
							
								
								
									
										5
									
								
								esphome/components/mipi_dsi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/mipi_dsi/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| CODEOWNERS = ["@clydebarrow"] | ||||
|  | ||||
| mipi_dsi_ns = cg.esphome_ns.namespace("mipi_dsi") | ||||
							
								
								
									
										232
									
								
								esphome/components/mipi_dsi/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								esphome/components/mipi_dsi/display.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,232 @@ | ||||
| import importlib | ||||
| import logging | ||||
| import pkgutil | ||||
|  | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import display | ||||
| from esphome.components.const import ( | ||||
|     BYTE_ORDER_BIG, | ||||
|     BYTE_ORDER_LITTLE, | ||||
|     CONF_BYTE_ORDER, | ||||
|     CONF_DRAW_ROUNDING, | ||||
| ) | ||||
| from esphome.components.display import CONF_SHOW_TEST_CARD | ||||
| from esphome.components.esp32 import const, only_on_variant | ||||
| from esphome.components.mipi import ( | ||||
|     COLOR_ORDERS, | ||||
|     CONF_COLOR_DEPTH, | ||||
|     CONF_HSYNC_BACK_PORCH, | ||||
|     CONF_HSYNC_FRONT_PORCH, | ||||
|     CONF_HSYNC_PULSE_WIDTH, | ||||
|     CONF_PCLK_FREQUENCY, | ||||
|     CONF_PIXEL_MODE, | ||||
|     CONF_USE_AXIS_FLIPS, | ||||
|     CONF_VSYNC_BACK_PORCH, | ||||
|     CONF_VSYNC_FRONT_PORCH, | ||||
|     CONF_VSYNC_PULSE_WIDTH, | ||||
|     MODE_BGR, | ||||
|     PIXEL_MODE_16BIT, | ||||
|     PIXEL_MODE_24BIT, | ||||
|     DriverChip, | ||||
|     dimension_schema, | ||||
|     get_color_depth, | ||||
|     map_sequence, | ||||
|     power_of_two, | ||||
|     requires_buffer, | ||||
| ) | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_COLOR_ORDER, | ||||
|     CONF_DIMENSIONS, | ||||
|     CONF_ENABLE_PIN, | ||||
|     CONF_ID, | ||||
|     CONF_INIT_SEQUENCE, | ||||
|     CONF_INVERT_COLORS, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_MIRROR_X, | ||||
|     CONF_MIRROR_Y, | ||||
|     CONF_MODEL, | ||||
|     CONF_RESET_PIN, | ||||
|     CONF_ROTATION, | ||||
|     CONF_SWAP_XY, | ||||
|     CONF_TRANSFORM, | ||||
|     CONF_WIDTH, | ||||
| ) | ||||
| from esphome.final_validate import full_config | ||||
|  | ||||
| from . import mipi_dsi_ns, models | ||||
|  | ||||
| DEPENDENCIES = ["esp32"] | ||||
| DOMAIN = "mipi_dsi" | ||||
|  | ||||
| LOGGER = logging.getLogger(DOMAIN) | ||||
|  | ||||
| MIPI_DSI = mipi_dsi_ns.class_("MIPI_DSI", display.Display, cg.Component) | ||||
| ColorOrder = display.display_ns.enum("ColorMode") | ||||
| ColorBitness = display.display_ns.enum("ColorBitness") | ||||
|  | ||||
| CONF_LANE_BIT_RATE = "lane_bit_rate" | ||||
| CONF_LANES = "lanes" | ||||
|  | ||||
| DriverChip("CUSTOM") | ||||
|  | ||||
| # Import all models dynamically from the models package | ||||
|  | ||||
| for module_info in pkgutil.iter_modules(models.__path__): | ||||
|     importlib.import_module(f".models.{module_info.name}", package=__package__) | ||||
|  | ||||
| MODELS = DriverChip.get_models() | ||||
|  | ||||
| COLOR_DEPTHS = { | ||||
|     16: ColorBitness.COLOR_BITNESS_565, | ||||
|     24: ColorBitness.COLOR_BITNESS_888, | ||||
| } | ||||
|  | ||||
|  | ||||
| def model_schema(config): | ||||
|     model = MODELS[config[CONF_MODEL].upper()] | ||||
|     transform = cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_MIRROR_X): cv.boolean, | ||||
|             cv.Required(CONF_MIRROR_Y): cv.boolean, | ||||
|         } | ||||
|     ) | ||||
|     if model.get_default(CONF_SWAP_XY) != cv.UNDEFINED: | ||||
|         transform = transform.extend( | ||||
|             { | ||||
|                 cv.Optional(CONF_SWAP_XY): cv.invalid( | ||||
|                     "Axis swapping not supported by this model" | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|     else: | ||||
|         transform = transform.extend( | ||||
|             { | ||||
|                 cv.Required(CONF_SWAP_XY): cv.boolean, | ||||
|             } | ||||
|         ) | ||||
|     # CUSTOM model will need to provide a custom init sequence | ||||
|     iseqconf = ( | ||||
|         cv.Required(CONF_INIT_SEQUENCE) | ||||
|         if model.initsequence is None | ||||
|         else cv.Optional(CONF_INIT_SEQUENCE) | ||||
|     ) | ||||
|     swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False) | ||||
|  | ||||
|     # Dimensions are optional if the model has a default width and the swap_xy transform is not overridden | ||||
|     cv_dimensions = ( | ||||
|         cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required | ||||
|     ) | ||||
|     pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_24BIT, "16", "24") | ||||
|     schema = display.FULL_DISPLAY_SCHEMA.extend( | ||||
|         { | ||||
|             model.option(CONF_RESET_PIN, cv.UNDEFINED): pins.gpio_output_pin_schema, | ||||
|             cv.GenerateID(): cv.declare_id(MIPI_DSI), | ||||
|             cv_dimensions(CONF_DIMENSIONS): dimension_schema( | ||||
|                 model.get_default(CONF_DRAW_ROUNDING, 1) | ||||
|             ), | ||||
|             model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list( | ||||
|                 pins.gpio_output_pin_schema | ||||
|             ), | ||||
|             model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(COLOR_ORDERS, upper=True), | ||||
|             model.option(CONF_DRAW_ROUNDING, 2): power_of_two, | ||||
|             model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of( | ||||
|                 *pixel_modes, lower=True | ||||
|             ), | ||||
|             model.option(CONF_TRANSFORM, cv.UNDEFINED): transform, | ||||
|             cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), | ||||
|             model.option(CONF_INVERT_COLORS, False): cv.boolean, | ||||
|             model.option(CONF_COLOR_DEPTH, "16"): cv.one_of( | ||||
|                 *[str(d) for d in COLOR_DEPTHS], | ||||
|                 *[f"{d}bit" for d in COLOR_DEPTHS], | ||||
|                 lower=True, | ||||
|             ), | ||||
|             model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean, | ||||
|             model.option(CONF_PCLK_FREQUENCY, "40MHz"): cv.All( | ||||
|                 cv.frequency, cv.Range(min=4e6, max=100e6) | ||||
|             ), | ||||
|             model.option(CONF_LANES, 2): cv.int_range(1, 2), | ||||
|             model.option(CONF_LANE_BIT_RATE, None): cv.All( | ||||
|                 cv.bps, cv.Range(min=100e6, max=3200e6) | ||||
|             ), | ||||
|             iseqconf: cv.ensure_list(map_sequence), | ||||
|             model.option(CONF_BYTE_ORDER, BYTE_ORDER_LITTLE): cv.one_of( | ||||
|                 BYTE_ORDER_LITTLE, BYTE_ORDER_BIG, lower=True | ||||
|             ), | ||||
|             model.option(CONF_HSYNC_PULSE_WIDTH): cv.int_, | ||||
|             model.option(CONF_HSYNC_BACK_PORCH): cv.int_, | ||||
|             model.option(CONF_HSYNC_FRONT_PORCH): cv.int_, | ||||
|             model.option(CONF_VSYNC_PULSE_WIDTH): cv.int_, | ||||
|             model.option(CONF_VSYNC_BACK_PORCH): cv.int_, | ||||
|             model.option(CONF_VSYNC_FRONT_PORCH): cv.int_, | ||||
|         } | ||||
|     ) | ||||
|     return cv.All( | ||||
|         schema, | ||||
|         only_on_variant(supported=[const.VARIANT_ESP32P4]), | ||||
|         cv.only_with_esp_idf, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def _config_schema(config): | ||||
|     config = cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True), | ||||
|         }, | ||||
|         extra=cv.ALLOW_EXTRA, | ||||
|     )(config) | ||||
|     return model_schema(config)(config) | ||||
|  | ||||
|  | ||||
| def _final_validate(config): | ||||
|     global_config = full_config.get() | ||||
|  | ||||
|     from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN | ||||
|  | ||||
|     if not requires_buffer(config) and LVGL_DOMAIN not in global_config: | ||||
|         # If no drawing methods are configured, and LVGL is not enabled, show a test card | ||||
|         config[CONF_SHOW_TEST_CARD] = True | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = _config_schema | ||||
| FINAL_VALIDATE_SCHEMA = _final_validate | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     model = MODELS[config[CONF_MODEL].upper()] | ||||
|     color_depth = COLOR_DEPTHS[get_color_depth(config)] | ||||
|     pixel_mode = int(config[CONF_PIXEL_MODE].removesuffix("bit")) | ||||
|     width, height, _offset_width, _offset_height = model.get_dimensions(config) | ||||
|     var = cg.new_Pvariable(config[CONF_ID], width, height, color_depth, pixel_mode) | ||||
|  | ||||
|     sequence, madctl = model.get_sequence(config) | ||||
|     cg.add(var.set_model(config[CONF_MODEL])) | ||||
|     cg.add(var.set_init_sequence(sequence)) | ||||
|     cg.add(var.set_madctl(madctl)) | ||||
|     cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) | ||||
|     cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) | ||||
|     cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) | ||||
|     cg.add(var.set_hsync_front_porch(config[CONF_HSYNC_FRONT_PORCH])) | ||||
|     cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH])) | ||||
|     cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH])) | ||||
|     cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) | ||||
|     cg.add(var.set_pclk_frequency(int(config[CONF_PCLK_FREQUENCY] / 1e6))) | ||||
|     cg.add(var.set_lanes(int(config[CONF_LANES]))) | ||||
|     cg.add(var.set_lane_bit_rate(int(config[CONF_LANE_BIT_RATE] / 1e6))) | ||||
|     if reset_pin := config.get(CONF_RESET_PIN): | ||||
|         reset = await cg.gpio_pin_expression(reset_pin) | ||||
|         cg.add(var.set_reset_pin(reset)) | ||||
|     if enable_pin := config.get(CONF_ENABLE_PIN): | ||||
|         enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin] | ||||
|         cg.add(var.set_enable_pins(enable)) | ||||
|  | ||||
|     if model.rotation_as_transform(config): | ||||
|         config[CONF_ROTATION] = 0 | ||||
|     await display.register_display(var, config) | ||||
|     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_)) | ||||
							
								
								
									
										379
									
								
								esphome/components/mipi_dsi/mipi_dsi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								esphome/components/mipi_dsi/mipi_dsi.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,379 @@ | ||||
| #ifdef USE_ESP32_VARIANT_ESP32P4 | ||||
| #include <utility> | ||||
| #include "mipi_dsi.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace mipi_dsi { | ||||
|  | ||||
| static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { | ||||
|   auto *sem = static_cast<SemaphoreHandle_t *>(user_ctx); | ||||
|   BaseType_t need_yield = pdFALSE; | ||||
|   xSemaphoreGiveFromISR(sem, &need_yield); | ||||
|   return (need_yield == pdTRUE); | ||||
| } | ||||
| void MIPI_DSI::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Running Setup"); | ||||
|  | ||||
|   if (!this->enable_pins_.empty()) { | ||||
|     for (auto *pin : this->enable_pins_) { | ||||
|       pin->setup(); | ||||
|       pin->digital_write(true); | ||||
|     } | ||||
|     delay(10); | ||||
|   } | ||||
|  | ||||
|   esp_lcd_dsi_bus_config_t bus_config = { | ||||
|       .bus_id = 0,  // index from 0, specify the DSI host to use | ||||
|       .num_data_lanes = | ||||
|           this->lanes_,  // Number of data lanes to use, can't set a value that exceeds the chip's capability | ||||
|       .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,  // Clock source for the DPHY | ||||
|       .lane_bit_rate_mbps = this->lane_bit_rate_,   // Bit rate of the data lanes, in Mbps | ||||
|   }; | ||||
|   auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_); | ||||
|   if (err != ESP_OK) { | ||||
|     this->smark_failed("lcd_new_dsi_bus failed", err); | ||||
|     return; | ||||
|   } | ||||
|   esp_lcd_dbi_io_config_t dbi_config = { | ||||
|       .virtual_channel = 0, | ||||
|       .lcd_cmd_bits = 8,    // according to the LCD spec | ||||
|       .lcd_param_bits = 8,  // according to the LCD spec | ||||
|   }; | ||||
|   err = esp_lcd_new_panel_io_dbi(this->bus_handle_, &dbi_config, &this->io_handle_); | ||||
|   if (err != ESP_OK) { | ||||
|     this->smark_failed("new_panel_io_dbi failed", err); | ||||
|     return; | ||||
|   } | ||||
|   auto pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; | ||||
|   if (this->color_depth_ == display::COLOR_BITNESS_888) { | ||||
|     pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888; | ||||
|   } | ||||
|   esp_lcd_dpi_panel_config_t dpi_config = {.virtual_channel = 0, | ||||
|                                            .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, | ||||
|                                            .dpi_clock_freq_mhz = this->pclk_frequency_, | ||||
|                                            .pixel_format = pixel_format, | ||||
|                                            .num_fbs = 1,  // number of frame buffers to allocate | ||||
|                                            .video_timing = | ||||
|                                                { | ||||
|                                                    .h_size = this->width_, | ||||
|                                                    .v_size = this->height_, | ||||
|                                                    .hsync_pulse_width = this->hsync_pulse_width_, | ||||
|                                                    .hsync_back_porch = this->hsync_back_porch_, | ||||
|                                                    .hsync_front_porch = this->hsync_front_porch_, | ||||
|                                                    .vsync_pulse_width = this->vsync_pulse_width_, | ||||
|                                                    .vsync_back_porch = this->vsync_back_porch_, | ||||
|                                                    .vsync_front_porch = this->vsync_front_porch_, | ||||
|                                                }, | ||||
|                                            .flags = { | ||||
|                                                .use_dma2d = true, | ||||
|                                            }}; | ||||
|   err = esp_lcd_new_panel_dpi(this->bus_handle_, &dpi_config, &this->handle_); | ||||
|   if (err != ESP_OK) { | ||||
|     this->smark_failed("esp_lcd_new_panel_dpi failed", err); | ||||
|     return; | ||||
|   } | ||||
|   if (this->reset_pin_ != nullptr) { | ||||
|     this->reset_pin_->setup(); | ||||
|     this->reset_pin_->digital_write(true); | ||||
|     delay(5); | ||||
|     this->reset_pin_->digital_write(false); | ||||
|     delay(5); | ||||
|     this->reset_pin_->digital_write(true); | ||||
|   } else { | ||||
|     esp_lcd_panel_io_tx_param(this->io_handle_, SW_RESET_CMD, nullptr, 0); | ||||
|   } | ||||
|   // need to know when the display is ready for SLPOUT command - will be 120ms after reset | ||||
|   auto when = millis() + 120; | ||||
|   err = esp_lcd_panel_init(this->handle_); | ||||
|   if (err != ESP_OK) { | ||||
|     this->smark_failed("esp_lcd_init failed", err); | ||||
|     return; | ||||
|   } | ||||
|   size_t index = 0; | ||||
|   auto &vec = this->init_sequence_; | ||||
|   while (index != vec.size()) { | ||||
|     if (vec.size() - index < 2) { | ||||
|       this->mark_failed("Malformed init sequence"); | ||||
|       return; | ||||
|     } | ||||
|     uint8_t cmd = vec[index++]; | ||||
|     uint8_t x = vec[index++]; | ||||
|     if (x == DELAY_FLAG) { | ||||
|       ESP_LOGD(TAG, "Delay %dms", cmd); | ||||
|       delay(cmd); | ||||
|     } else { | ||||
|       uint8_t num_args = x & 0x7F; | ||||
|       if (vec.size() - index < num_args) { | ||||
|         this->mark_failed("Malformed init sequence"); | ||||
|         return; | ||||
|       } | ||||
|       if (cmd == SLEEP_OUT) { | ||||
|         // are we ready, boots? | ||||
|         int duration = when - millis(); | ||||
|         if (duration > 0) { | ||||
|           delay(duration); | ||||
|         } | ||||
|       } | ||||
|       const auto *ptr = vec.data() + index; | ||||
|       ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args, | ||||
|                 format_hex_pretty(ptr, num_args, '.', false).c_str()); | ||||
|       err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args); | ||||
|       if (err != ESP_OK) { | ||||
|         this->smark_failed("lcd_panel_io_tx_param failed", err); | ||||
|         return; | ||||
|       } | ||||
|       index += num_args; | ||||
|       if (cmd == SLEEP_OUT) | ||||
|         delay(10); | ||||
|     } | ||||
|   } | ||||
|   this->io_lock_ = xSemaphoreCreateBinary(); | ||||
|   esp_lcd_dpi_panel_event_callbacks_t cbs = { | ||||
|       .on_color_trans_done = notify_refresh_ready, | ||||
|   }; | ||||
|  | ||||
|   err = (esp_lcd_dpi_panel_register_event_callbacks(this->handle_, &cbs, this->io_lock_)); | ||||
|   if (err != ESP_OK) { | ||||
|     this->smark_failed("Failed to register callbacks", err); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "MIPI DSI setup complete"); | ||||
| } | ||||
|  | ||||
| void MIPI_DSI::update() { | ||||
|   if (this->auto_clear_enabled_) { | ||||
|     this->clear(); | ||||
|   } | ||||
|   if (this->show_test_card_) { | ||||
|     this->test_card(); | ||||
|   } else if (this->page_ != nullptr) { | ||||
|     this->page_->get_writer()(*this); | ||||
|   } else if (this->writer_.has_value()) { | ||||
|     (*this->writer_)(*this); | ||||
|   } else { | ||||
|     this->stop_poller(); | ||||
|   } | ||||
|   if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) | ||||
|     return; | ||||
|   ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_); | ||||
|   int w = this->x_high_ - this->x_low_ + 1; | ||||
|   int h = this->y_high_ - this->y_low_ + 1; | ||||
|   this->write_to_display_(this->x_low_, this->y_low_, w, h, this->buffer_, this->x_low_, this->y_low_, | ||||
|                           this->width_ - w - this->x_low_); | ||||
|   // invalidate watermarks | ||||
|   this->x_low_ = this->width_; | ||||
|   this->y_low_ = this->height_; | ||||
|   this->x_high_ = 0; | ||||
|   this->y_high_ = 0; | ||||
| } | ||||
|  | ||||
| void MIPI_DSI::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) { | ||||
|   if (w <= 0 || h <= 0) | ||||
|     return; | ||||
|   // if color mapping is required, pass the buck. | ||||
|   // note that endianness is not considered here - it is assumed to match! | ||||
|   if (bitness != this->color_depth_) { | ||||
|     display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, | ||||
|                                      x_pad); | ||||
|   } | ||||
|   this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); | ||||
| } | ||||
|  | ||||
| void MIPI_DSI::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, | ||||
|                                  int x_pad) { | ||||
|   esp_err_t err = ESP_OK; | ||||
|   auto bytes_per_pixel = 3 - this->color_depth_; | ||||
|   auto stride = (x_offset + w + x_pad) * bytes_per_pixel; | ||||
|   ptr += y_offset * stride + x_offset * bytes_per_pixel;  // skip to the first pixel | ||||
|   // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. | ||||
|   if (x_offset == 0 && x_pad == 0) { | ||||
|     err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr); | ||||
|     xSemaphoreTake(this->io_lock_, portMAX_DELAY); | ||||
|  | ||||
|   } else { | ||||
|     // draw line by line | ||||
|     for (int y = 0; y != h; y++) { | ||||
|       err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr); | ||||
|       if (err != ESP_OK) | ||||
|         break; | ||||
|       ptr += stride;  // next line | ||||
|       xSemaphoreTake(this->io_lock_, portMAX_DELAY); | ||||
|     } | ||||
|   } | ||||
|   if (err != ESP_OK) | ||||
|     ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); | ||||
| } | ||||
|  | ||||
| bool MIPI_DSI::check_buffer_() { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|   if (this->buffer_ != nullptr) | ||||
|     return true; | ||||
|   // this is dependent on the enum values. | ||||
|   auto bytes_per_pixel = 3 - this->color_depth_; | ||||
|   RAMAllocator<uint8_t> allocator; | ||||
|   this->buffer_ = allocator.allocate(this->height_ * this->width_ * bytes_per_pixel); | ||||
|   if (this->buffer_ == nullptr) { | ||||
|     this->mark_failed("Could not allocate buffer for display!"); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void MIPI_DSI::draw_pixel_at(int x, int y, Color color) { | ||||
|   if (!this->get_clipping().inside(x, y)) | ||||
|     return; | ||||
|  | ||||
|   switch (this->rotation_) { | ||||
|     case display::DISPLAY_ROTATION_0_DEGREES: | ||||
|       break; | ||||
|     case display::DISPLAY_ROTATION_90_DEGREES: | ||||
|       std::swap(x, y); | ||||
|       x = this->width_ - x - 1; | ||||
|       break; | ||||
|     case display::DISPLAY_ROTATION_180_DEGREES: | ||||
|       x = this->width_ - x - 1; | ||||
|       y = this->height_ - y - 1; | ||||
|       break; | ||||
|     case display::DISPLAY_ROTATION_270_DEGREES: | ||||
|       std::swap(x, y); | ||||
|       y = this->height_ - y - 1; | ||||
|       break; | ||||
|   } | ||||
|   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { | ||||
|     return; | ||||
|   } | ||||
|   auto pixel = convert_big_endian(display::ColorUtil::color_to_565(color)); | ||||
|   if (!this->check_buffer_()) | ||||
|     return; | ||||
|   size_t pos = (y * this->width_) + x; | ||||
|   switch (this->color_depth_) { | ||||
|     case display::COLOR_BITNESS_565: { | ||||
|       auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_); | ||||
|       uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5); | ||||
|       uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3); | ||||
|       uint16_t new_color = lo_byte | (hi_byte << 8);  // little endian | ||||
|       if (ptr_16[pos] == new_color) | ||||
|         return; | ||||
|       ptr_16[pos] = new_color; | ||||
|       break; | ||||
|     } | ||||
|     case display::COLOR_BITNESS_888: | ||||
|       if (this->color_mode_ == display::COLOR_ORDER_BGR) { | ||||
|         this->buffer_[pos * 3] = color.b; | ||||
|         this->buffer_[pos * 3 + 1] = color.g; | ||||
|         this->buffer_[pos * 3 + 2] = color.r; | ||||
|       } else { | ||||
|         this->buffer_[pos * 3] = color.r; | ||||
|         this->buffer_[pos * 3 + 1] = color.g; | ||||
|         this->buffer_[pos * 3 + 2] = color.b; | ||||
|       } | ||||
|       break; | ||||
|     case display::COLOR_BITNESS_332: | ||||
|       break; | ||||
|   } | ||||
|   // low and high watermark may speed up drawing from buffer | ||||
|   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 MIPI_DSI::fill(Color color) { | ||||
|   if (!this->check_buffer_()) | ||||
|     return; | ||||
|   switch (this->color_depth_) { | ||||
|     case display::COLOR_BITNESS_565: { | ||||
|       auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_); | ||||
|       uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5); | ||||
|       uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3); | ||||
|       uint16_t new_color = lo_byte | (hi_byte << 8);  // little endian | ||||
|       std::fill_n(ptr_16, this->width_ * this->height_, new_color); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case display::COLOR_BITNESS_888: | ||||
|       if (this->color_mode_ == display::COLOR_ORDER_BGR) { | ||||
|         for (size_t i = 0; i != this->width_ * this->height_; i++) { | ||||
|           this->buffer_[i * 3 + 0] = color.b; | ||||
|           this->buffer_[i * 3 + 1] = color.g; | ||||
|           this->buffer_[i * 3 + 2] = color.r; | ||||
|         } | ||||
|       } else { | ||||
|         for (size_t i = 0; i != this->width_ * this->height_; i++) { | ||||
|           this->buffer_[i * 3 + 0] = color.r; | ||||
|           this->buffer_[i * 3 + 1] = color.g; | ||||
|           this->buffer_[i * 3 + 2] = color.b; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| int MIPI_DSI::get_width() { | ||||
|   switch (this->rotation_) { | ||||
|     case display::DISPLAY_ROTATION_90_DEGREES: | ||||
|     case display::DISPLAY_ROTATION_270_DEGREES: | ||||
|       return this->get_height_internal(); | ||||
|     case display::DISPLAY_ROTATION_0_DEGREES: | ||||
|     case display::DISPLAY_ROTATION_180_DEGREES: | ||||
|     default: | ||||
|       return this->get_width_internal(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| int MIPI_DSI::get_height() { | ||||
|   switch (this->rotation_) { | ||||
|     case display::DISPLAY_ROTATION_0_DEGREES: | ||||
|     case display::DISPLAY_ROTATION_180_DEGREES: | ||||
|       return this->get_height_internal(); | ||||
|     case display::DISPLAY_ROTATION_90_DEGREES: | ||||
|     case display::DISPLAY_ROTATION_270_DEGREES: | ||||
|     default: | ||||
|       return this->get_width_internal(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| static const uint8_t PIXEL_MODES[] = {0, 16, 18, 24}; | ||||
|  | ||||
| void MIPI_DSI::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "MIPI_DSI RGB LCD" | ||||
|                 "\n  Model: %s" | ||||
|                 "\n  Width: %u" | ||||
|                 "\n  Height: %u" | ||||
|                 "\n  Mirror X: %s" | ||||
|                 "\n  Mirror Y: %s" | ||||
|                 "\n  Swap X/Y: %s" | ||||
|                 "\n  Rotation: %d degrees" | ||||
|                 "\n  DSI Lanes: %u" | ||||
|                 "\n  Lane Bit Rate: %uMbps" | ||||
|                 "\n  HSync Pulse Width: %u" | ||||
|                 "\n  HSync Back Porch: %u" | ||||
|                 "\n  HSync Front Porch: %u" | ||||
|                 "\n  VSync Pulse Width: %u" | ||||
|                 "\n  VSync Back Porch: %u" | ||||
|                 "\n  VSync Front Porch: %u" | ||||
|                 "\n  Buffer Color Depth: %d bit" | ||||
|                 "\n  Display Pixel Mode: %d bit" | ||||
|                 "\n  Color Order: %s" | ||||
|                 "\n  Invert Colors: %s" | ||||
|                 "\n  Pixel Clock: %dMHz", | ||||
|                 this->model_, this->width_, this->height_, YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)), | ||||
|                 YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY)), YESNO(this->madctl_ & MADCTL_MV), this->rotation_, | ||||
|                 this->lanes_, this->lane_bit_rate_, this->hsync_pulse_width_, this->hsync_back_porch_, | ||||
|                 this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_, this->vsync_front_porch_, | ||||
|                 (3 - this->color_depth_) * 8, this->pixel_mode_, this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", | ||||
|                 YESNO(this->invert_colors_), this->pclk_frequency_); | ||||
|   LOG_PIN("  Reset Pin ", this->reset_pin_); | ||||
| } | ||||
| }  // namespace mipi_dsi | ||||
| }  // namespace esphome | ||||
| #endif  // USE_ESP32_VARIANT_ESP32P4 | ||||
							
								
								
									
										123
									
								
								esphome/components/mipi_dsi/mipi_dsi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/mipi_dsi/mipi_dsi.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| // | ||||
| // Created by Clyde Stubbs on 29/10/2023. | ||||
| // | ||||
| #pragma once | ||||
|  | ||||
| // only applicable on ESP32-P4 | ||||
| #ifdef USE_ESP32_VARIANT_ESP32P4 | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/gpio.h" | ||||
|  | ||||
| #include "esphome/components/display/display.h" | ||||
| #include "esp_lcd_panel_ops.h" | ||||
| #include "esp_lcd_panel_io.h" | ||||
|  | ||||
| #include "esp_lcd_mipi_dsi.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace mipi_dsi { | ||||
|  | ||||
| constexpr static const char *const TAG = "display.mipi_dsi"; | ||||
| const uint8_t SW_RESET_CMD = 0x01; | ||||
| const uint8_t SLEEP_OUT = 0x11; | ||||
| const uint8_t SDIR_CMD = 0xC7; | ||||
| const uint8_t MADCTL_CMD = 0x36; | ||||
| const uint8_t INVERT_OFF = 0x20; | ||||
| const uint8_t INVERT_ON = 0x21; | ||||
| const uint8_t DISPLAY_ON = 0x29; | ||||
| const uint8_t CMD2_BKSEL = 0xFF; | ||||
| const uint8_t DELAY_FLAG = 0xFF; | ||||
| const uint8_t MADCTL_BGR = 0x08; | ||||
| const uint8_t MADCTL_MX = 0x40; | ||||
| const uint8_t MADCTL_MY = 0x80; | ||||
| const uint8_t MADCTL_MV = 0x20;     // row/column swap | ||||
| const uint8_t MADCTL_XFLIP = 0x02;  // Mirror the display horizontally | ||||
| const uint8_t MADCTL_YFLIP = 0x01;  // Mirror the display vertically | ||||
|  | ||||
| class MIPI_DSI : public display::Display { | ||||
|  public: | ||||
|   MIPI_DSI(size_t width, size_t height, display::ColorBitness color_depth, uint8_t pixel_mode) | ||||
|       : width_(width), height_(height), color_depth_(color_depth), pixel_mode_(pixel_mode) {} | ||||
|   display::ColorOrder get_color_mode() { return this->color_mode_; } | ||||
|   void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } | ||||
|   void set_invert_colors(bool invert_colors) { this->invert_colors_ = invert_colors; } | ||||
|   display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } | ||||
|  | ||||
|   void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } | ||||
|   void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); } | ||||
|   void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; } | ||||
|   int get_width_internal() override { return this->width_; } | ||||
|   int get_height_internal() override { return this->height_; } | ||||
|   void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; } | ||||
|   void set_hsync_front_porch(uint16_t hsync_front_porch) { this->hsync_front_porch_ = hsync_front_porch; } | ||||
|   void set_hsync_pulse_width(uint16_t hsync_pulse_width) { this->hsync_pulse_width_ = hsync_pulse_width; } | ||||
|   void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } | ||||
|   void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } | ||||
|   void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } | ||||
|   void set_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; } | ||||
|   void set_model(const char *model) { this->model_ = model; } | ||||
|   void set_lane_bit_rate(uint16_t lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; } | ||||
|   void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } | ||||
|   void set_madctl(uint8_t madctl) { this->madctl_ = madctl; } | ||||
|  | ||||
|   void smark_failed(const char *message, esp_err_t err) { | ||||
|     auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err)); | ||||
|     this->mark_failed(str.c_str()); | ||||
|   } | ||||
|  | ||||
|   void update() 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 fill(Color color) override; | ||||
|   int get_width() override; | ||||
|   int get_height() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, | ||||
|                          int x_pad); | ||||
|   bool check_buffer_(); | ||||
|   GPIOPin *reset_pin_{nullptr}; | ||||
|   std::vector<GPIOPin *> enable_pins_{}; | ||||
|   size_t width_{}; | ||||
|   size_t height_{}; | ||||
|   uint8_t madctl_{}; | ||||
|   uint16_t hsync_pulse_width_ = 10; | ||||
|   uint16_t hsync_back_porch_ = 10; | ||||
|   uint16_t hsync_front_porch_ = 20; | ||||
|   uint16_t vsync_pulse_width_ = 10; | ||||
|   uint16_t vsync_back_porch_ = 10; | ||||
|   uint16_t vsync_front_porch_ = 10; | ||||
|   const char *model_{"Unknown"}; | ||||
|   std::vector<uint8_t> init_sequence_{}; | ||||
|   uint16_t pclk_frequency_ = 16;  // in MHz | ||||
|   uint16_t lane_bit_rate_{1500};  // in Mbps | ||||
|   uint8_t lanes_{2};              // 1, 2, 3 or 4 lanes | ||||
|  | ||||
|   bool invert_colors_{}; | ||||
|   display::ColorOrder color_mode_{display::COLOR_ORDER_BGR}; | ||||
|   display::ColorBitness color_depth_; | ||||
|   uint8_t pixel_mode_{}; | ||||
|  | ||||
|   esp_lcd_panel_handle_t handle_{}; | ||||
|   esp_lcd_dsi_bus_handle_t bus_handle_{}; | ||||
|   esp_lcd_panel_io_handle_t io_handle_{}; | ||||
|   SemaphoreHandle_t io_lock_{}; | ||||
|   uint8_t *buffer_{nullptr}; | ||||
|   uint16_t x_low_{1}; | ||||
|   uint16_t y_low_{1}; | ||||
|   uint16_t x_high_{0}; | ||||
|   uint16_t y_high_{0}; | ||||
| }; | ||||
|  | ||||
| }  // namespace mipi_dsi | ||||
| }  // namespace esphome | ||||
| #endif | ||||
							
								
								
									
										0
									
								
								esphome/components/mipi_dsi/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/mipi_dsi/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										38
									
								
								esphome/components/mipi_dsi/models/guition.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/mipi_dsi/models/guition.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| from esphome.components.mipi import DriverChip | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| # fmt: off | ||||
| DriverChip( | ||||
|     "JC1060P470", | ||||
|     width=1024, | ||||
|     height=600, | ||||
|     hsync_back_porch=160, | ||||
|     hsync_pulse_width=40, | ||||
|     hsync_front_porch=160, | ||||
|     vsync_back_porch=23, | ||||
|     vsync_pulse_width=10, | ||||
|     vsync_front_porch=12, | ||||
|     pclk_frequency="54MHz", | ||||
|     lane_bit_rate="750Mbps", | ||||
|     swap_xy=cv.UNDEFINED, | ||||
|     color_order="RGB", | ||||
|     reset_pin=27, | ||||
|     initsequence=[ | ||||
|         (0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00), | ||||
|         (0x0B, 0x11), (0x17, 0x00), (0x20, 0x04), (0x1F, 0x05), (0x23, 0x00), (0x25, 0x19), (0x28, 0x18), (0x29, 0x04), (0x2A, 0x01), | ||||
|         (0x2B, 0x04), (0x2C, 0x01), (0x30, 0x02), (0x01, 0x22), (0x03, 0x12), (0x04, 0x00), (0x05, 0x64), (0x0A, 0x08), | ||||
|         (0x0B, 0x0A, 0x1A, 0x0B, 0x0D, 0x0D, 0x11, 0x10, 0x06, 0x08, 0x1F, 0x1D), | ||||
|         (0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D), | ||||
|         (0x0D, 0x16, 0x1B, 0x0B, 0x0D, 0x0D, 0x11, 0x10, 0x07, 0x09, 0x1E, 0x1C), | ||||
|         (0x0E, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D), | ||||
|         (0x0F, 0x16, 0x1B, 0x0D, 0x0B, 0x0D, 0x11, 0x10, 0x1C, 0x1E, 0x09, 0x07), | ||||
|         (0x10, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D), | ||||
|         (0x11, 0x0A, 0x1A, 0x0D, 0x0B, 0x0D, 0x11, 0x10, 0x1D, 0x1F, 0x08, 0x06), | ||||
|         (0x12, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D), | ||||
|         (0x14, 0x00, 0x00, 0x11, 0x11), (0x18, 0x99), (0x30, 0x06), | ||||
|         (0x12, 0x36, 0x2C, 0x2E, 0x3C, 0x38, 0x35, 0x35, 0x32, 0x2E, 0x1D, 0x2B, 0x21, 0x16, 0x29,), | ||||
|         (0x13, 0x36, 0x2C, 0x2E, 0x3C, 0x38, 0x35, 0x35, 0x32, 0x2E, 0x1D, 0x2B, 0x21, 0x16, 0x29,), | ||||
|         (0x30, 0x0A), (0x02, 0x4F), (0x0B, 0x40), (0x12, 0x3E), (0x13, 0x78), (0x30, 0x0D), (0x0D, 0x04), | ||||
|         (0x10, 0x0C), (0x11, 0x0C), (0x12, 0x0C), (0x13, 0x0C), (0x30, 0x00), | ||||
|     ], | ||||
| ) | ||||
							
								
								
									
										57
									
								
								esphome/components/mipi_dsi/models/m5stack.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/mipi_dsi/models/m5stack.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| from esphome.components.mipi import DriverChip | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| # fmt: off | ||||
| DriverChip( | ||||
|     "M5STACK-TAB5", | ||||
|     height=1280, | ||||
|     width=720, | ||||
|     hsync_back_porch=140, | ||||
|     hsync_pulse_width=40, | ||||
|     hsync_front_porch=40, | ||||
|     vsync_back_porch=20, | ||||
|     vsync_pulse_width=4, | ||||
|     vsync_front_porch=20, | ||||
|     pclk_frequency="60MHz", | ||||
|     lane_bit_rate="730Mbps", | ||||
|     swap_xy=cv.UNDEFINED, | ||||
|     color_order="RGB", | ||||
|     initsequence=[ | ||||
|         (0xFF, 0x98, 0x81, 0x01),  # Select Page 1 | ||||
|         (0xB7, 0x03),  # Pad control - 2 lane | ||||
|         (0xFF, 0x98, 0x81, 0x00),  # Select Page 0 | ||||
|         # CMD_Page 3 | ||||
|         (0xFF, 0x98, 0x81, 0x03),  # Select Page 3 | ||||
|         (0x01, 0x00), (0x02, 0x00), (0x03, 0x73), (0x04, 0x00), (0x05, 0x00), (0x06, 0x08), (0x07, 0x00), (0x08, 0x00), | ||||
|         (0x09, 0x1B), (0x0A, 0x01), (0x0B, 0x01), (0x0C, 0x0D), (0x0D, 0x01), (0x0E, 0x01), (0x0F, 0x26), (0x10, 0x26), | ||||
|         (0x11, 0x00), (0x12, 0x00), (0x13, 0x02), (0x14, 0x00), (0x15, 0x00), (0x16, 0x00), (0x17, 0x00), (0x18, 0x00), | ||||
|         (0x19, 0x00), (0x1A, 0x00), (0x1B, 0x00), (0x1C, 0x00), (0x1D, 0x00), (0x1E, 0x40), (0x1F, 0x00), (0x20, 0x06), | ||||
|         (0x21, 0x01), (0x22, 0x00), (0x23, 0x00), (0x24, 0x00), (0x25, 0x00), (0x26, 0x00), (0x27, 0x00), (0x28, 0x33), | ||||
|         (0x29, 0x03), (0x2A, 0x00), (0x2B, 0x00), (0x2C, 0x00), (0x2D, 0x00), (0x2E, 0x00), (0x2F, 0x00), (0x30, 0x00), | ||||
|         (0x31, 0x00), (0x32, 0x00), (0x33, 0x00), (0x34, 0x00), (0x35, 0x00), (0x36, 0x00), (0x37, 0x00), (0x38, 0x00), | ||||
|         (0x39, 0x00), (0x3A, 0x00), (0x3B, 0x00), (0x3C, 0x00), (0x3D, 0x00), (0x3E, 0x00), (0x3F, 0x00), (0x40, 0x00), | ||||
|         (0x41, 0x00), (0x42, 0x00), (0x43, 0x00), (0x44, 0x00), (0x50, 0x01), (0x51, 0x23), (0x52, 0x45), (0x53, 0x67), | ||||
|         (0x54, 0x89), (0x55, 0xAB), (0x56, 0x01), (0x57, 0x23), (0x58, 0x45), (0x59, 0x67), (0x5A, 0x89), (0x5B, 0xAB), | ||||
|         (0x5C, 0xCD), (0x5D, 0xEF), (0x5E, 0x11), (0x5F, 0x02), (0x60, 0x00), (0x61, 0x07), (0x62, 0x06), (0x63, 0x0E), | ||||
|         (0x64, 0x0F), (0x65, 0x0C), (0x66, 0x0D), (0x67, 0x02), (0x68, 0x02), (0x69, 0x02), (0x6A, 0x02), (0x6B, 0x02), | ||||
|         (0x6C, 0x02), (0x6D, 0x02), (0x6E, 0x02), (0x6F, 0x02), (0x70, 0x02), (0x71, 0x02), (0x72, 0x02), (0x73, 0x05), | ||||
|         (0x74, 0x01), (0x75, 0x02), (0x76, 0x00), (0x77, 0x07), (0x78, 0x06), (0x79, 0x0E), (0x7A, 0x0F), (0x7B, 0x0C), | ||||
|         (0x7C, 0x0D), (0x7D, 0x02), (0x7E, 0x02), (0x7F, 0x02), (0x80, 0x02), (0x81, 0x02), (0x82, 0x02), (0x83, 0x02), | ||||
|         (0x84, 0x02), (0x85, 0x02), (0x86, 0x02), (0x87, 0x02), (0x88, 0x02), (0x89, 0x05), (0x8A, 0x01), | ||||
|         (0xFF, 0x98, 0x81, 0x04),  # Select Page 4 | ||||
|         (0x38, 0x01), (0x39, 0x00), (0x6C, 0x15), (0x6E, 0x1A), (0x6F, 0x25), (0x3A, 0xA4), (0x8D, 0x20), (0x87, 0xBA), (0x3B, 0x98), | ||||
|         (0xFF, 0x98, 0x81, 0x01),  # Select Page 1 | ||||
|         (0x22, 0x0A), (0x31, 0x00), (0x50, 0x6B), (0x51, 0x66), (0x53, 0x73), (0x55, 0x8B), (0x60, 0x1B), (0x61, 0x01), (0x62, 0x0C), (0x63, 0x00), | ||||
|         # Gamma P | ||||
|         (0xA0, 0x00), (0xA1, 0x15), (0xA2, 0x1F), (0xA3, 0x13), (0xA4, 0x11), (0xA5, 0x21), (0xA6, 0x17), (0xA7, 0x1B), | ||||
|         (0xA8, 0x6B), (0xA9, 0x1E), (0xAA, 0x2B), (0xAB, 0x5D), (0xAC, 0x19), (0xAD, 0x14), (0xAE, 0x4B), (0xAF, 0x1D), | ||||
|         (0xB0, 0x27), (0xB1, 0x49), (0xB2, 0x5D), (0xB3, 0x39), | ||||
|         # Gamma N | ||||
|         (0xC0, 0x00), (0xC1, 0x01), (0xC2, 0x0C), (0xC3, 0x11), (0xC4, 0x15), (0xC5, 0x28), (0xC6, 0x1B), (0xC7, 0x1C), | ||||
|         (0xC8, 0x62), (0xC9, 0x1C), (0xCA, 0x29), (0xCB, 0x60), (0xCC, 0x16), (0xCD, 0x17), (0xCE, 0x4A), (0xCF, 0x23), | ||||
|         (0xD0, 0x24), (0xD1, 0x4F), (0xD2, 0x5F), (0xD3, 0x39), | ||||
|         # CMD_Page 0 | ||||
|         (0xFF, 0x98, 0x81, 0x00),  # Select Page 0 | ||||
|         (0x35,), (0xFE,), | ||||
|     ], | ||||
| ) | ||||
							
								
								
									
										105
									
								
								esphome/components/mipi_dsi/models/waveshare.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								esphome/components/mipi_dsi/models/waveshare.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| from esphome.components.mipi import DriverChip | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| # fmt: off | ||||
| DriverChip( | ||||
|     "WAVESHARE-P4-NANO-10.1", | ||||
|     height=1280, | ||||
|     width=800, | ||||
|     hsync_back_porch=20, | ||||
|     hsync_pulse_width=20, | ||||
|     hsync_front_porch=40, | ||||
|     vsync_back_porch=12, | ||||
|     vsync_pulse_width=4, | ||||
|     vsync_front_porch=30, | ||||
|     pclk_frequency="80MHz", | ||||
|     lane_bit_rate="1.5Gbps", | ||||
|     swap_xy=cv.UNDEFINED, | ||||
|     color_order="RGB", | ||||
|     initsequence=[ | ||||
|         (0xE0, 0x00),  # select userpage | ||||
|         (0xE1, 0x93), (0xE2, 0x65), (0xE3, 0xF8), | ||||
|         (0x80, 0x01),  # Select number of lanes (2) | ||||
|         (0xE0, 0x01),  # select page 1 | ||||
|         (0x00, 0x00), (0x01, 0x38), (0x03, 0x10), (0x04, 0x38), (0x0C, 0x74), (0x17, 0x00), (0x18, 0xAF), (0x19, 0x00), | ||||
|         (0x1A, 0x00), (0x1B, 0xAF), (0x1C, 0x00), (0x35, 0x26), (0x37, 0x09), (0x38, 0x04), (0x39, 0x00), (0x3C, 0x78), | ||||
|         (0x3D, 0xFF), (0x3E, 0xFF), (0x3F, 0x7F), (0x40, 0x06), (0x41, 0xA0), (0x42, 0x81), (0x43, 0x1E), (0x44, 0x0D), | ||||
|         (0x45, 0x28), (0x55, 0x02), (0x57, 0x69), (0x59, 0x0A), (0x5A, 0x2A), (0x5B, 0x17), (0x5D, 0x7F), (0x5E, 0x6A), | ||||
|         (0x5F, 0x5B), (0x60, 0x4F), (0x61, 0x4A), (0x62, 0x3D), (0x63, 0x41), (0x64, 0x2A), (0x65, 0x44), (0x66, 0x43), | ||||
|         (0x67, 0x44), (0x68, 0x62), (0x69, 0x52), (0x6A, 0x59), (0x6B, 0x4C), (0x6C, 0x48), (0x6D, 0x3A), (0x6E, 0x26), | ||||
|         (0x6F, 0x00), (0x70, 0x7F), (0x71, 0x6A), (0x72, 0x5B), (0x73, 0x4F), (0x74, 0x4A), (0x75, 0x3D), (0x76, 0x41), | ||||
|         (0x77, 0x2A), (0x78, 0x44), (0x79, 0x43), (0x7A, 0x44), (0x7B, 0x62), (0x7C, 0x52), (0x7D, 0x59), (0x7E, 0x4C), | ||||
|         (0x7F, 0x48), (0x80, 0x3A), (0x81, 0x26), (0x82, 0x00), | ||||
|         (0xE0, 0x02),  # select page 2 | ||||
|         (0x00, 0x42), (0x01, 0x42), (0x02, 0x40), (0x03, 0x40), (0x04, 0x5E), (0x05, 0x5E), (0x06, 0x5F), (0x07, 0x5F), | ||||
|         (0x08, 0x5F), (0x09, 0x57), (0x0A, 0x57), (0x0B, 0x77), (0x0C, 0x77), (0x0D, 0x47), (0x0E, 0x47), (0x0F, 0x45), | ||||
|         (0x10, 0x45), (0x11, 0x4B), (0x12, 0x4B), (0x13, 0x49), (0x14, 0x49), (0x15, 0x5F), (0x16, 0x41), (0x17, 0x41), | ||||
|         (0x18, 0x40), (0x19, 0x40), (0x1A, 0x5E), (0x1B, 0x5E), (0x1C, 0x5F), (0x1D, 0x5F), (0x1E, 0x5F), (0x1F, 0x57), | ||||
|         (0x20, 0x57), (0x21, 0x77), (0x22, 0x77), (0x23, 0x46), (0x24, 0x46), (0x25, 0x44), (0x26, 0x44), (0x27, 0x4A), | ||||
|         (0x28, 0x4A), (0x29, 0x48), (0x2A, 0x48), (0x2B, 0x5F), (0x2C, 0x01), (0x2D, 0x01), (0x2E, 0x00), (0x2F, 0x00), | ||||
|         (0x30, 0x1F), (0x31, 0x1F), (0x32, 0x1E), (0x33, 0x1E), (0x34, 0x1F), (0x35, 0x17), (0x36, 0x17), (0x37, 0x37), | ||||
|         (0x38, 0x37), (0x39, 0x08), (0x3A, 0x08), (0x3B, 0x0A), (0x3C, 0x0A), (0x3D, 0x04), (0x3E, 0x04), (0x3F, 0x06), | ||||
|         (0x40, 0x06), (0x41, 0x1F), (0x42, 0x02), (0x43, 0x02), (0x44, 0x00), (0x45, 0x00), (0x46, 0x1F), (0x47, 0x1F), | ||||
|         (0x48, 0x1E), (0x49, 0x1E), (0x4A, 0x1F), (0x4B, 0x17), (0x4C, 0x17), (0x4D, 0x37), (0x4E, 0x37), (0x4F, 0x09), | ||||
|         (0x50, 0x09), (0x51, 0x0B), (0x52, 0x0B), (0x53, 0x05), (0x54, 0x05), (0x55, 0x07), (0x56, 0x07), (0x57, 0x1F), | ||||
|         (0x58, 0x40), (0x5B, 0x30), (0x5C, 0x00), (0x5D, 0x34), (0x5E, 0x05), (0x5F, 0x02), (0x63, 0x00), (0x64, 0x6A), | ||||
|         (0x67, 0x73), (0x68, 0x07), (0x69, 0x08), (0x6A, 0x6A), (0x6B, 0x08), (0x6C, 0x00), (0x6D, 0x00), (0x6E, 0x00), | ||||
|         (0x6F, 0x88), (0x75, 0xFF), (0x77, 0xDD), (0x78, 0x2C), (0x79, 0x15), (0x7A, 0x17), (0x7D, 0x14), (0x7E, 0x82), | ||||
|         (0xE0, 0x04),  # select page 4 | ||||
|         (0x00, 0x0E), (0x02, 0xB3), (0x09, 0x61), (0x0E, 0x48), (0x37, 0x58), (0x2B, 0x0F), | ||||
|         (0xE0, 0x00),  # Select userpage | ||||
|         (0xE6, 0x02), (0xE7, 0x0C), | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| DriverChip( | ||||
|     "WAVESHARE-P4-86-PANEL", | ||||
|     height=720, | ||||
|     width=720, | ||||
|     hsync_back_porch=80, | ||||
|     hsync_pulse_width=20, | ||||
|     hsync_front_porch=80, | ||||
|     vsync_back_porch=12, | ||||
|     vsync_pulse_width=4, | ||||
|     vsync_front_porch=30, | ||||
|     pclk_frequency="46MHz", | ||||
|     lane_bit_rate="1Gbps", | ||||
|     swap_xy=cv.UNDEFINED, | ||||
|     color_order="RGB", | ||||
|     reset_pin=27, | ||||
|     initsequence=[ | ||||
|         (0xB9, 0xF1, 0x12, 0x83), | ||||
|         ( | ||||
|             0xBA, 0x31, 0x81, 0x05, 0xF9, 0x0E, 0x0E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x25, 0x00, | ||||
|             0x90, 0x0A, 0x00, 0x00, 0x01, 0x4F, 0x01, 0x00, 0x00, 0x37, | ||||
|         ), | ||||
|         (0xB8, 0x25, 0x22, 0xF0, 0x63), | ||||
|         (0xBF, 0x02, 0x11, 0x00), | ||||
|         (0xB3, 0x10, 0x10, 0x28, 0x28, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00), | ||||
|         (0xC0, 0x73, 0x73, 0x50, 0x50, 0x00, 0x00, 0x12, 0x70, 0x00), | ||||
|         (0xBC, 0x46), (0xCC, 0x0B), (0xB4, 0x80), (0xB2, 0x3C, 0x12, 0x30), | ||||
|         (0xE3, 0x07, 0x07, 0x0B, 0x0B, 0x03, 0x0B, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xC0, 0x10,), | ||||
|         (0xC1, 0x36, 0x00, 0x32, 0x32, 0x77, 0xF1, 0xCC, 0xCC, 0x77, 0x77, 0x33, 0x33), | ||||
|         (0xB5, 0x0A, 0x0A), | ||||
|         (0xB6, 0xB2, 0xB2), | ||||
|         ( | ||||
|             0xE9, 0xC8, 0x10, 0x0A, 0x10, 0x0F, 0xA1, 0x80, 0x12, 0x31, 0x23, 0x47, 0x86, 0xA1, 0x80, | ||||
|             0x47, 0x08, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x48, | ||||
|             0x02, 0x8B, 0xAF, 0x46, 0x02, 0x88, 0x88, 0x88, 0x88, 0x88, 0x48, 0x13, 0x8B, 0xAF, 0x57, | ||||
|             0x13, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|             0x00, 0x00, 0x00, 0x00, | ||||
|         ), | ||||
|         ( | ||||
|             0xEA, 0x96, 0x12, 0x01, 0x01, 0x01, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x31, | ||||
|             0x8B, 0xA8, 0x31, 0x75, 0x88, 0x88, 0x88, 0x88, 0x88, 0x4F, 0x20, 0x8B, 0xA8, 0x20, 0x64, | ||||
|             0x88, 0x88, 0x88, 0x88, 0x88, 0x23, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xA1, 0x80, 0x00, 0x00, | ||||
|             0x00, 0x00, | ||||
|         ), | ||||
|         ( | ||||
|             0xE0, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13, 0x15, 0x14, | ||||
|             0x15, 0x10, 0x17, 0x00, 0x0A, 0x0F, 0x29, 0x3B, 0x3F, 0x42, 0x39, 0x06, 0x0D, 0x10, 0x13, | ||||
|             0x15, 0x14, 0x15, 0x10, 0x17, | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
							
								
								
									
										0
									
								
								esphome/components/mipi_rgb/models/lilygo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/mipi_rgb/models/lilygo.py
									
									
									
									
									
										Normal file
									
								
							| @@ -106,7 +106,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type | ||||
|     // Calculate random offset (0 to min(interval/2, 5s)) | ||||
|     uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float()); | ||||
|     item->next_execution_ = now + offset; | ||||
|     ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", name_cstr, delay, offset); | ||||
|     ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", name_cstr ? name_cstr : "", delay, | ||||
|              offset); | ||||
|   } else { | ||||
|     item->interval = 0; | ||||
|     item->next_execution_ = now + delay; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user