mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' of https://github.com/esphome/esphome into usb-uart
This commit is contained in:
		| @@ -1,3 +1,5 @@ | |||||||
|  | import logging | ||||||
|  |  | ||||||
| from esphome import automation, pins | from esphome import automation, pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import esp32, esp32_rmt, remote_base | from esphome.components import esp32, esp32_rmt, remote_base | ||||||
| @@ -18,9 +20,12 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| AUTO_LOAD = ["remote_base"] | AUTO_LOAD = ["remote_base"] | ||||||
|  |  | ||||||
| CONF_EOT_LEVEL = "eot_level" | CONF_EOT_LEVEL = "eot_level" | ||||||
|  | CONF_NON_BLOCKING = "non_blocking" | ||||||
| CONF_ON_TRANSMIT = "on_transmit" | CONF_ON_TRANSMIT = "on_transmit" | ||||||
| CONF_ON_COMPLETE = "on_complete" | CONF_ON_COMPLETE = "on_complete" | ||||||
| CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID | CONF_TRANSMITTER_ID = remote_base.CONF_TRANSMITTER_ID | ||||||
| @@ -65,11 +70,25 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|             esp32_c6=48, |             esp32_c6=48, | ||||||
|             esp32_h2=48, |             esp32_h2=48, | ||||||
|         ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), |         ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), | ||||||
|  |         cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean), | ||||||
|         cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), |         cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), | ||||||
|         cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), |         cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_non_blocking(config): | ||||||
|  |     if CORE.is_esp32 and CONF_NON_BLOCKING not in config: | ||||||
|  |         _LOGGER.warning( | ||||||
|  |             "'non_blocking' is not set for 'remote_transmitter' and will default to 'true'.\n" | ||||||
|  |             "The default behavior changed in 2025.11.0; previously blocking mode was used.\n" | ||||||
|  |             "To silence this warning, explicitly set 'non_blocking: true' (or 'false')." | ||||||
|  |         ) | ||||||
|  |         config[CONF_NON_BLOCKING] = True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _validate_non_blocking | ||||||
|  |  | ||||||
| DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value( | DIGITAL_WRITE_ACTION_SCHEMA = cv.maybe_simple_value( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent), |         cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterComponent), | ||||||
| @@ -95,6 +114,7 @@ async def to_code(config): | |||||||
|     if CORE.is_esp32: |     if CORE.is_esp32: | ||||||
|         var = cg.new_Pvariable(config[CONF_ID], pin) |         var = cg.new_Pvariable(config[CONF_ID], pin) | ||||||
|         cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) |         cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) | ||||||
|  |         cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING])) | ||||||
|         if CONF_CLOCK_RESOLUTION in config: |         if CONF_CLOCK_RESOLUTION in config: | ||||||
|             cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) |             cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) | ||||||
|         if CONF_USE_DMA in config: |         if CONF_USE_DMA in config: | ||||||
|   | |||||||
| @@ -54,6 +54,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
| #if defined(USE_ESP32) | #if defined(USE_ESP32) | ||||||
|   void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } |   void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } | ||||||
|   void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } |   void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } | ||||||
|  |   void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; |   Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; | ||||||
| @@ -74,6 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   void configure_rmt_(); |   void configure_rmt_(); | ||||||
|  |   void wait_for_rmt_(); | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) | ||||||
|   RemoteTransmitterComponentStore store_{}; |   RemoteTransmitterComponentStore store_{}; | ||||||
| @@ -90,6 +92,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
|   esp_err_t error_code_{ESP_OK}; |   esp_err_t error_code_{ESP_OK}; | ||||||
|   std::string error_string_{""}; |   std::string error_string_{""}; | ||||||
|   bool inverted_{false}; |   bool inverted_{false}; | ||||||
|  |   bool non_blocking_{false}; | ||||||
| #endif | #endif | ||||||
|   uint8_t carrier_duty_percent_; |   uint8_t carrier_duty_percent_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -196,12 +196,29 @@ void RemoteTransmitterComponent::configure_rmt_() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void RemoteTransmitterComponent::wait_for_rmt_() { | ||||||
|  |   esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->complete_trigger_->trigger(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) | ||||||
| void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | ||||||
|  |   uint64_t total_duration = 0; | ||||||
|  |  | ||||||
|   if (this->is_failed()) { |   if (this->is_failed()) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // if the timeout was cancelled, block until the tx is complete | ||||||
|  |   if (this->non_blocking_ && this->cancel_timeout("complete")) { | ||||||
|  |     this->wait_for_rmt_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { |   if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { | ||||||
|     this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); |     this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); | ||||||
|     this->configure_rmt_(); |     this->configure_rmt_(); | ||||||
| @@ -212,6 +229,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|  |  | ||||||
|   // encode any delay at the start of the buffer to simplify the encoder callback |   // encode any delay at the start of the buffer to simplify the encoder callback | ||||||
|   // this will be skipped the first time around |   // this will be skipped the first time around | ||||||
|  |   total_duration += send_wait * (send_times - 1); | ||||||
|   send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait)); |   send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait)); | ||||||
|   while (send_wait > 0) { |   while (send_wait > 0) { | ||||||
|     int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); |     int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); | ||||||
| @@ -229,6 +247,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|     if (!level) { |     if (!level) { | ||||||
|       value = -value; |       value = -value; | ||||||
|     } |     } | ||||||
|  |     total_duration += value * send_times; | ||||||
|     value = this->from_microseconds_(static_cast<uint32_t>(value)); |     value = this->from_microseconds_(static_cast<uint32_t>(value)); | ||||||
|     while (value > 0) { |     while (value > 0) { | ||||||
|       int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); |       int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); | ||||||
| @@ -260,13 +279,12 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|   } else { |   } else { | ||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|   } |   } | ||||||
|   error = rmt_tx_wait_all_done(this->channel_, -1); |  | ||||||
|   if (error != ESP_OK) { |  | ||||||
|     ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); |  | ||||||
|     this->status_set_warning(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   this->complete_trigger_->trigger(); |   if (this->non_blocking_) { | ||||||
|  |     this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); }); | ||||||
|  |   } else { | ||||||
|  |     this->wait_for_rmt_(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| #else | #else | ||||||
| void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | ||||||
|   | |||||||
| @@ -319,6 +319,9 @@ def iter_ids(config, path=None): | |||||||
|             yield from iter_ids(item, path + [i]) |             yield from iter_ids(item, path + [i]) | ||||||
|     elif isinstance(config, dict): |     elif isinstance(config, dict): | ||||||
|         for key, value in config.items(): |         for key, value in config.items(): | ||||||
|  |             if len(path) == 0 and key == CONF_SUBSTITUTIONS: | ||||||
|  |                 # Ignore IDs in substitution definitions. | ||||||
|  |                 continue | ||||||
|             if isinstance(key, core.ID): |             if isinstance(key, core.ID): | ||||||
|                 yield key, path |                 yield key, path | ||||||
|             yield from iter_ids(value, path + [key]) |             yield from iter_ids(value, path + [key]) | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ remote_transmitter: | |||||||
|   - id: xmitr |   - id: xmitr | ||||||
|     pin: ${pin} |     pin: ${pin} | ||||||
|     carrier_duty_percent: 50% |     carrier_duty_percent: 50% | ||||||
|  |     non_blocking: true | ||||||
|     clock_resolution: ${clock_resolution} |     clock_resolution: ${clock_resolution} | ||||||
|     rmt_symbols: ${rmt_symbols} |     rmt_symbols: ${rmt_symbols} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -261,6 +261,17 @@ def test_device_duplicate_id( | |||||||
|     assert "ID duplicate_device redefined!" in captured.out |     assert "ID duplicate_device redefined!" in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_substitution_with_id( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that a ids coming from substitutions do not cause false positive ID redefinition.""" | ||||||
|  |     load_config_from_fixture( | ||||||
|  |         yaml_file, "id_collision_with_substitution.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert "ID some_switch_id redefined!" not in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_add_platform_defines_priority() -> None: | def test_add_platform_defines_priority() -> None: | ||||||
|     """Test that _add_platform_defines runs after globals. |     """Test that _add_platform_defines runs after globals. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | esphome: | ||||||
|  |   name: test | ||||||
|  |  | ||||||
|  | host: | ||||||
|  |  | ||||||
|  | substitutions: | ||||||
|  |   support_switches: | ||||||
|  |     - platform: gpio | ||||||
|  |       id: some_switch_id | ||||||
|  |       pin: 12 | ||||||
|  |  | ||||||
|  | switch: $support_switches | ||||||
		Reference in New Issue
	
	Block a user