mirror of
https://github.com/esphome/esphome.git
synced 2025-07-27 05:36:38 +00:00
commit
2b5cceda58
2
Doxyfile
2
Doxyfile
@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.7.2
|
PROJECT_NUMBER = 2025.7.3
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, i2c
|
from esphome.components import esp32, i2c
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
|
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET, Framework
|
||||||
|
|
||||||
CODEOWNERS = ["@trvrnrth"]
|
CODEOWNERS = ["@trvrnrth"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@ -56,7 +56,15 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
): cv.positive_time_period_minutes,
|
): cv.positive_time_period_minutes,
|
||||||
}
|
}
|
||||||
).extend(i2c.i2c_device_schema(0x76)),
|
).extend(i2c.i2c_device_schema(0x76)),
|
||||||
cv.only_with_arduino,
|
cv.only_with_framework(
|
||||||
|
frameworks=Framework.ARDUINO,
|
||||||
|
suggestions={
|
||||||
|
Framework.ESP_IDF: (
|
||||||
|
"bme68x_bsec2_i2c",
|
||||||
|
"sensor/bme68x_bsec2",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
cv.Any(
|
cv.Any(
|
||||||
cv.only_on_esp8266,
|
cv.only_on_esp8266,
|
||||||
cv.All(
|
cv.All(
|
||||||
|
@ -16,6 +16,8 @@ namespace esp32_touch {
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_touch";
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
|
static const uint32_t SETUP_MODE_THRESHOLD = 0xFFFF;
|
||||||
|
|
||||||
void ESP32TouchComponent::setup() {
|
void ESP32TouchComponent::setup() {
|
||||||
// Create queue for touch events
|
// Create queue for touch events
|
||||||
// Queue size calculation: children * 4 allows for burst scenarios where ISR
|
// Queue size calculation: children * 4 allows for burst scenarios where ISR
|
||||||
@ -44,8 +46,12 @@ void ESP32TouchComponent::setup() {
|
|||||||
|
|
||||||
// Configure each touch pad
|
// Configure each touch pad
|
||||||
for (auto *child : this->children_) {
|
for (auto *child : this->children_) {
|
||||||
|
if (this->setup_mode_) {
|
||||||
|
touch_pad_config(child->get_touch_pad(), SETUP_MODE_THRESHOLD);
|
||||||
|
} else {
|
||||||
touch_pad_config(child->get_touch_pad(), child->get_threshold());
|
touch_pad_config(child->get_touch_pad(), child->get_threshold());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Register ISR handler
|
// Register ISR handler
|
||||||
esp_err_t err = touch_pad_isr_register(touch_isr_handler, this);
|
esp_err_t err = touch_pad_isr_register(touch_isr_handler, this);
|
||||||
@ -114,8 +120,8 @@ void ESP32TouchComponent::loop() {
|
|||||||
child->publish_state(new_state);
|
child->publish_state(new_state);
|
||||||
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
||||||
// Note: ESP32 v1 uses inverted logic - touched when value < threshold
|
// Note: ESP32 v1 uses inverted logic - touched when value < threshold
|
||||||
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")",
|
ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 " < threshold: %" PRIu32 ")",
|
||||||
child->get_name().c_str(), event.value, child->get_threshold());
|
child->get_name().c_str(), ONOFF(new_state), event.value, child->get_threshold());
|
||||||
}
|
}
|
||||||
break; // Exit inner loop after processing matching pad
|
break; // Exit inner loop after processing matching pad
|
||||||
}
|
}
|
||||||
@ -188,11 +194,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
// as any pad remains touched. This allows us to detect both new touches and
|
// as any pad remains touched. This allows us to detect both new touches and
|
||||||
// continued touches, but releases must be detected by timeout in the main loop.
|
// continued touches, but releases must be detected by timeout in the main loop.
|
||||||
|
|
||||||
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
|
||||||
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
|
||||||
// Therefore: touched = (value < threshold)
|
|
||||||
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
|
||||||
|
|
||||||
// Process all configured pads to check their current state
|
// Process all configured pads to check their current state
|
||||||
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
||||||
// so we must scan all configured pads to find which ones were touched
|
// so we must scan all configured pads to find which ones were touched
|
||||||
@ -211,11 +212,16 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip pads that aren’t in the trigger mask
|
// Skip pads that aren’t in the trigger mask
|
||||||
bool is_touched = (mask >> pad) & 1;
|
if (((mask >> pad) & 1) == 0) {
|
||||||
if (!is_touched) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
||||||
|
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
||||||
|
// Therefore: touched = (value < threshold)
|
||||||
|
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
||||||
|
bool is_touched = value < child->get_threshold();
|
||||||
|
|
||||||
// Always send the current state - the main loop will filter for changes
|
// Always send the current state - the main loop will filter for changes
|
||||||
// We send both touched and untouched states because the ISR doesn't
|
// We send both touched and untouched states because the ISR doesn't
|
||||||
// track previous state (to keep ISR fast and simple)
|
// track previous state (to keep ISR fast and simple)
|
||||||
|
@ -2,7 +2,13 @@ from esphome import pins
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import fastled_base
|
from esphome.components import fastled_base
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_CHIPSET, CONF_NUM_LEDS, CONF_PIN, CONF_RGB_ORDER
|
from esphome.const import (
|
||||||
|
CONF_CHIPSET,
|
||||||
|
CONF_NUM_LEDS,
|
||||||
|
CONF_PIN,
|
||||||
|
CONF_RGB_ORDER,
|
||||||
|
Framework,
|
||||||
|
)
|
||||||
|
|
||||||
AUTO_LOAD = ["fastled_base"]
|
AUTO_LOAD = ["fastled_base"]
|
||||||
|
|
||||||
@ -48,13 +54,22 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
|
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
_validate,
|
cv.only_with_framework(
|
||||||
|
frameworks=Framework.ARDUINO,
|
||||||
|
suggestions={
|
||||||
|
Framework.ESP_IDF: (
|
||||||
|
"esp32_rmt_led_strip",
|
||||||
|
"light/esp32_rmt_led_strip",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
cv.require_framework_version(
|
cv.require_framework_version(
|
||||||
esp8266_arduino=cv.Version(2, 7, 4),
|
esp8266_arduino=cv.Version(2, 7, 4),
|
||||||
esp32_arduino=cv.Version(99, 0, 0),
|
esp32_arduino=cv.Version(99, 0, 0),
|
||||||
max_version=True,
|
max_version=True,
|
||||||
extra_message="Please see note on documentation for FastLED",
|
extra_message="Please see note on documentation for FastLED",
|
||||||
),
|
),
|
||||||
|
_validate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from esphome.const import (
|
|||||||
CONF_DATA_RATE,
|
CONF_DATA_RATE,
|
||||||
CONF_NUM_LEDS,
|
CONF_NUM_LEDS,
|
||||||
CONF_RGB_ORDER,
|
CONF_RGB_ORDER,
|
||||||
|
Framework,
|
||||||
)
|
)
|
||||||
|
|
||||||
AUTO_LOAD = ["fastled_base"]
|
AUTO_LOAD = ["fastled_base"]
|
||||||
@ -33,6 +34,15 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.only_with_framework(
|
||||||
|
frameworks=Framework.ARDUINO,
|
||||||
|
suggestions={
|
||||||
|
Framework.ESP_IDF: (
|
||||||
|
"spi_led_strip",
|
||||||
|
"light/spi_led_strip",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
cv.require_framework_version(
|
cv.require_framework_version(
|
||||||
esp8266_arduino=cv.Version(2, 7, 4),
|
esp8266_arduino=cv.Version(2, 7, 4),
|
||||||
esp32_arduino=cv.Version(99, 0, 0),
|
esp32_arduino=cv.Version(99, 0, 0),
|
||||||
|
@ -4,7 +4,13 @@ from esphome import pins
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
|
from esphome.const import (
|
||||||
|
CONF_ALLOW_OTHER_USES,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_NUMBER,
|
||||||
|
CONF_PIN,
|
||||||
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
from .. import gpio_ns
|
from .. import gpio_ns
|
||||||
@ -76,6 +82,18 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
use_interrupt = False
|
use_interrupt = False
|
||||||
|
|
||||||
|
# Check if pin is shared with other components (allow_other_uses)
|
||||||
|
# When a pin is shared, interrupts can interfere with other components
|
||||||
|
# (e.g., duty_cycle sensor) that need to monitor the pin's state changes
|
||||||
|
if use_interrupt and config[CONF_PIN].get(CONF_ALLOW_OTHER_USES, False):
|
||||||
|
_LOGGER.info(
|
||||||
|
"GPIO binary_sensor '%s': Disabling interrupts because pin %s is shared with other components. "
|
||||||
|
"The sensor will use polling mode for compatibility with other pin uses.",
|
||||||
|
config.get(CONF_NAME, config[CONF_ID]),
|
||||||
|
config[CONF_PIN][CONF_NUMBER],
|
||||||
|
)
|
||||||
|
use_interrupt = False
|
||||||
|
|
||||||
cg.add(var.set_use_interrupt(use_interrupt))
|
cg.add(var.set_use_interrupt(use_interrupt))
|
||||||
if use_interrupt:
|
if use_interrupt:
|
||||||
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
||||||
|
@ -15,6 +15,7 @@ from esphome.const import (
|
|||||||
CONF_PIN,
|
CONF_PIN,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_VARIANT,
|
CONF_VARIANT,
|
||||||
|
Framework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@ -162,7 +163,15 @@ def _validate_method(value):
|
|||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.only_with_arduino,
|
cv.only_with_framework(
|
||||||
|
frameworks=Framework.ARDUINO,
|
||||||
|
suggestions={
|
||||||
|
Framework.ESP_IDF: (
|
||||||
|
"esp32_rmt_led_strip",
|
||||||
|
"light/esp32_rmt_led_strip",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
cv.require_framework_version(
|
cv.require_framework_version(
|
||||||
esp8266_arduino=cv.Version(2, 4, 0),
|
esp8266_arduino=cv.Version(2, 4, 0),
|
||||||
esp32_arduino=cv.Version(0, 0, 0),
|
esp32_arduino=cv.Version(0, 0, 0),
|
||||||
|
@ -48,6 +48,9 @@ void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Sdl::draw_pixel_at(int x, int y, Color color) {
|
void Sdl::draw_pixel_at(int x, int y, Color color) {
|
||||||
|
if (!this->get_clipping().inside(x, y))
|
||||||
|
return;
|
||||||
|
|
||||||
SDL_Rect rect{x, y, 1, 1};
|
SDL_Rect rect{x, y, 1, 1};
|
||||||
auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB));
|
auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB));
|
||||||
SDL_UpdateTexture(this->texture_, &rect, &data, 2);
|
SDL_UpdateTexture(this->texture_, &rect, &data, 2);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import fan
|
from esphome.components import fan
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_OUTPUT_ID, CONF_SPEED_COUNT, CONF_SWITCH_DATAPOINT
|
from esphome.const import CONF_ID, CONF_SPEED_COUNT, CONF_SWITCH_DATAPOINT
|
||||||
|
|
||||||
from .. import CONF_TUYA_ID, Tuya, tuya_ns
|
from .. import CONF_TUYA_ID, Tuya, tuya_ns
|
||||||
|
|
||||||
@ -14,9 +14,9 @@ CONF_DIRECTION_DATAPOINT = "direction_datapoint"
|
|||||||
TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan)
|
TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
fan.FAN_SCHEMA.extend(
|
fan.fan_schema(TuyaFan)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaFan),
|
|
||||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||||
cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t,
|
||||||
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
|
||||||
@ -24,7 +24,8 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
|
||||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
|
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.has_at_least_one_key(CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT),
|
cv.has_at_least_one_key(CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
parent = await cg.get_variable(config[CONF_TUYA_ID])
|
parent = await cg.get_variable(config[CONF_TUYA_ID])
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], parent, config[CONF_SPEED_COUNT])
|
var = cg.new_Pvariable(config[CONF_ID], parent, config[CONF_SPEED_COUNT])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await fan.register_fan(var, config)
|
await fan.register_fan(var, config)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
|
|||||||
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
ESP_LOGD(TAG, "OTA in progress: %" PRIu32 " bytes read", this->ota_read_length_);
|
||||||
}
|
}
|
||||||
#ifdef USE_OTA_STATE_CALLBACK
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
// Report progress - use call_deferred since we're in web server task
|
// Report progress - use call_deferred since we're in web server task
|
||||||
@ -171,7 +171,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
|
|||||||
|
|
||||||
// Finalize
|
// Finalize
|
||||||
if (final) {
|
if (final) {
|
||||||
ESP_LOGD(TAG, "OTA final chunk: index=%zu, len=%zu, total_read=%u, contentLength=%zu", index, len,
|
ESP_LOGD(TAG, "OTA final chunk: index=%zu, len=%zu, total_read=%" PRIu32 ", contentLength=%zu", index, len,
|
||||||
this->ota_read_length_, request->contentLength());
|
this->ota_read_length_, request->contentLength());
|
||||||
|
|
||||||
// For Arduino framework, the Update library tracks expected size from firmware header
|
// For Arduino framework, the Update library tracks expected size from firmware header
|
||||||
|
@ -73,6 +73,7 @@ from esphome.const import (
|
|||||||
TYPE_GIT,
|
TYPE_GIT,
|
||||||
TYPE_LOCAL,
|
TYPE_LOCAL,
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS,
|
VALID_SUBSTITUTIONS_CHARACTERS,
|
||||||
|
Framework,
|
||||||
__version__ as ESPHOME_VERSION,
|
__version__ as ESPHOME_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import (
|
from esphome.core import (
|
||||||
@ -282,6 +283,38 @@ class FinalExternalInvalid(Invalid):
|
|||||||
"""Represents an invalid value in the final validation phase where the path should not be prepended."""
|
"""Represents an invalid value in the final validation phase where the path should not be prepended."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, order=True)
|
||||||
|
class Version:
|
||||||
|
major: int
|
||||||
|
minor: int
|
||||||
|
patch: int
|
||||||
|
extra: str = ""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.major}.{self.minor}.{self.patch}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, value: str) -> Version:
|
||||||
|
match = re.match(r"^(\d+).(\d+).(\d+)-?(\w*)$", value)
|
||||||
|
if match is None:
|
||||||
|
raise ValueError(f"Not a valid version number {value}")
|
||||||
|
major = int(match[1])
|
||||||
|
minor = int(match[2])
|
||||||
|
patch = int(match[3])
|
||||||
|
extra = match[4] or ""
|
||||||
|
return Version(major=major, minor=minor, patch=patch, extra=extra)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_beta(self) -> bool:
|
||||||
|
"""Check if this version is a beta version."""
|
||||||
|
return self.extra.startswith("b")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_dev(self) -> bool:
|
||||||
|
"""Check if this version is a development version."""
|
||||||
|
return self.extra.startswith("dev")
|
||||||
|
|
||||||
|
|
||||||
def check_not_templatable(value):
|
def check_not_templatable(value):
|
||||||
if isinstance(value, Lambda):
|
if isinstance(value, Lambda):
|
||||||
raise Invalid("This option is not templatable!")
|
raise Invalid("This option is not templatable!")
|
||||||
@ -619,16 +652,35 @@ def only_on(platforms):
|
|||||||
return validator_
|
return validator_
|
||||||
|
|
||||||
|
|
||||||
def only_with_framework(frameworks):
|
def only_with_framework(
|
||||||
|
frameworks: Framework | str | list[Framework | str], suggestions=None
|
||||||
|
):
|
||||||
"""Validate that this option can only be specified on the given frameworks."""
|
"""Validate that this option can only be specified on the given frameworks."""
|
||||||
if not isinstance(frameworks, list):
|
if not isinstance(frameworks, list):
|
||||||
frameworks = [frameworks]
|
frameworks = [frameworks]
|
||||||
|
|
||||||
|
frameworks = [Framework(framework) for framework in frameworks]
|
||||||
|
|
||||||
|
if suggestions is None:
|
||||||
|
suggestions = {}
|
||||||
|
|
||||||
|
version = Version.parse(ESPHOME_VERSION)
|
||||||
|
if version.is_beta:
|
||||||
|
docs_format = "https://beta.esphome.io/components/{path}"
|
||||||
|
elif version.is_dev:
|
||||||
|
docs_format = "https://next.esphome.io/components/{path}"
|
||||||
|
else:
|
||||||
|
docs_format = "https://esphome.io/components/{path}"
|
||||||
|
|
||||||
def validator_(obj):
|
def validator_(obj):
|
||||||
if CORE.target_framework not in frameworks:
|
if CORE.target_framework not in frameworks:
|
||||||
raise Invalid(
|
err_str = f"This feature is only available with framework(s) {', '.join([framework.value for framework in frameworks])}"
|
||||||
f"This feature is only available with frameworks {frameworks}"
|
if suggestion := suggestions.get(CORE.target_framework, None):
|
||||||
)
|
(component, docs_path) = suggestion
|
||||||
|
err_str += f"\nPlease use '{component}'"
|
||||||
|
if docs_path:
|
||||||
|
err_str += f": {docs_format.format(path=docs_path)}"
|
||||||
|
raise Invalid(err_str)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
return validator_
|
return validator_
|
||||||
@ -637,8 +689,8 @@ def only_with_framework(frameworks):
|
|||||||
only_on_esp32 = only_on(PLATFORM_ESP32)
|
only_on_esp32 = only_on(PLATFORM_ESP32)
|
||||||
only_on_esp8266 = only_on(PLATFORM_ESP8266)
|
only_on_esp8266 = only_on(PLATFORM_ESP8266)
|
||||||
only_on_rp2040 = only_on(PLATFORM_RP2040)
|
only_on_rp2040 = only_on(PLATFORM_RP2040)
|
||||||
only_with_arduino = only_with_framework("arduino")
|
only_with_arduino = only_with_framework(Framework.ARDUINO)
|
||||||
only_with_esp_idf = only_with_framework("esp-idf")
|
only_with_esp_idf = only_with_framework(Framework.ESP_IDF)
|
||||||
|
|
||||||
|
|
||||||
# Adapted from:
|
# Adapted from:
|
||||||
@ -1965,26 +2017,6 @@ def source_refresh(value: str):
|
|||||||
return positive_time_period_seconds(value)
|
return positive_time_period_seconds(value)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, order=True)
|
|
||||||
class Version:
|
|
||||||
major: int
|
|
||||||
minor: int
|
|
||||||
patch: int
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.major}.{self.minor}.{self.patch}"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse(cls, value: str) -> Version:
|
|
||||||
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
|
|
||||||
if match is None:
|
|
||||||
raise ValueError(f"Not a valid version number {value}")
|
|
||||||
major = int(match[1])
|
|
||||||
minor = int(match[2])
|
|
||||||
patch = int(match[3])
|
|
||||||
return Version(major=major, minor=minor, patch=patch)
|
|
||||||
|
|
||||||
|
|
||||||
def version_number(value):
|
def version_number(value):
|
||||||
value = string_strict(value)
|
value = string_strict(value)
|
||||||
try:
|
try:
|
||||||
|
@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.7.2"
|
__version__ = "2025.7.3"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -68,8 +68,11 @@ void Application::setup() {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
uint8_t new_app_state = STATUS_LED_WARNING;
|
||||||
this->scheduler.call();
|
uint32_t now = millis();
|
||||||
this->feed_wdt();
|
|
||||||
|
// Process pending loop enables to handle GPIO interrupts during setup
|
||||||
|
this->before_loop_tasks_(now);
|
||||||
|
|
||||||
for (uint32_t j = 0; j <= i; j++) {
|
for (uint32_t j = 0; j <= i; j++) {
|
||||||
// Update loop_component_start_time_ right before calling each component
|
// Update loop_component_start_time_ right before calling each component
|
||||||
this->loop_component_start_time_ = millis();
|
this->loop_component_start_time_ = millis();
|
||||||
@ -78,6 +81,8 @@ void Application::setup() {
|
|||||||
this->app_state_ |= new_app_state;
|
this->app_state_ |= new_app_state;
|
||||||
this->feed_wdt();
|
this->feed_wdt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->after_loop_tasks_();
|
||||||
this->app_state_ = new_app_state;
|
this->app_state_ = new_app_state;
|
||||||
yield();
|
yield();
|
||||||
} while (!component->can_proceed());
|
} while (!component->can_proceed());
|
||||||
@ -94,30 +99,10 @@ void Application::setup() {
|
|||||||
void Application::loop() {
|
void Application::loop() {
|
||||||
uint8_t new_app_state = 0;
|
uint8_t new_app_state = 0;
|
||||||
|
|
||||||
this->scheduler.call();
|
|
||||||
|
|
||||||
// Get the initial loop time at the start
|
// Get the initial loop time at the start
|
||||||
uint32_t last_op_end_time = millis();
|
uint32_t last_op_end_time = millis();
|
||||||
|
|
||||||
// Feed WDT with time
|
this->before_loop_tasks_(last_op_end_time);
|
||||||
this->feed_wdt(last_op_end_time);
|
|
||||||
|
|
||||||
// Process any pending enable_loop requests from ISRs
|
|
||||||
// This must be done before marking in_loop_ = true to avoid race conditions
|
|
||||||
if (this->has_pending_enable_loop_requests_) {
|
|
||||||
// Clear flag BEFORE processing to avoid race condition
|
|
||||||
// If ISR sets it during processing, we'll catch it next loop iteration
|
|
||||||
// This is safe because:
|
|
||||||
// 1. Each component has its own pending_enable_loop_ flag that we check
|
|
||||||
// 2. If we can't process a component (wrong state), enable_pending_loops_()
|
|
||||||
// will set this flag back to true
|
|
||||||
// 3. Any new ISR requests during processing will set the flag again
|
|
||||||
this->has_pending_enable_loop_requests_ = false;
|
|
||||||
this->enable_pending_loops_();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we're in the loop for safe reentrant modifications
|
|
||||||
this->in_loop_ = true;
|
|
||||||
|
|
||||||
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
|
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
|
||||||
this->current_loop_index_++) {
|
this->current_loop_index_++) {
|
||||||
@ -138,7 +123,7 @@ void Application::loop() {
|
|||||||
this->feed_wdt(last_op_end_time);
|
this->feed_wdt(last_op_end_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->in_loop_ = false;
|
this->after_loop_tasks_();
|
||||||
this->app_state_ = new_app_state;
|
this->app_state_ = new_app_state;
|
||||||
|
|
||||||
// Use the last component's end time instead of calling millis() again
|
// Use the last component's end time instead of calling millis() again
|
||||||
@ -400,6 +385,36 @@ void Application::enable_pending_loops_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::before_loop_tasks_(uint32_t loop_start_time) {
|
||||||
|
// Process scheduled tasks
|
||||||
|
this->scheduler.call();
|
||||||
|
|
||||||
|
// Feed the watchdog timer
|
||||||
|
this->feed_wdt(loop_start_time);
|
||||||
|
|
||||||
|
// Process any pending enable_loop requests from ISRs
|
||||||
|
// This must be done before marking in_loop_ = true to avoid race conditions
|
||||||
|
if (this->has_pending_enable_loop_requests_) {
|
||||||
|
// Clear flag BEFORE processing to avoid race condition
|
||||||
|
// If ISR sets it during processing, we'll catch it next loop iteration
|
||||||
|
// This is safe because:
|
||||||
|
// 1. Each component has its own pending_enable_loop_ flag that we check
|
||||||
|
// 2. If we can't process a component (wrong state), enable_pending_loops_()
|
||||||
|
// will set this flag back to true
|
||||||
|
// 3. Any new ISR requests during processing will set the flag again
|
||||||
|
this->has_pending_enable_loop_requests_ = false;
|
||||||
|
this->enable_pending_loops_();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark that we're in the loop for safe reentrant modifications
|
||||||
|
this->in_loop_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::after_loop_tasks_() {
|
||||||
|
// Clear the in_loop_ flag to indicate we're done processing components
|
||||||
|
this->in_loop_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||||
bool Application::register_socket_fd(int fd) {
|
bool Application::register_socket_fd(int fd) {
|
||||||
// WARNING: This function is NOT thread-safe and must only be called from the main loop
|
// WARNING: This function is NOT thread-safe and must only be called from the main loop
|
||||||
|
@ -504,6 +504,8 @@ class Application {
|
|||||||
void enable_component_loop_(Component *component);
|
void enable_component_loop_(Component *component);
|
||||||
void enable_pending_loops_();
|
void enable_pending_loops_();
|
||||||
void activate_looping_component_(uint16_t index);
|
void activate_looping_component_(uint16_t index);
|
||||||
|
void before_loop_tasks_(uint32_t loop_start_time);
|
||||||
|
void after_loop_tasks_();
|
||||||
|
|
||||||
void feed_wdt_arch_();
|
void feed_wdt_arch_();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user