mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +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