mirror of
https://github.com/esphome/esphome.git
synced 2025-11-02 16:11:53 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96c013eb1 | ||
|
|
58166b3e71 | ||
|
|
345fc0b6ca | ||
|
|
127058e700 | ||
|
|
57f7a709cf | ||
|
|
f2a9e9265e | ||
|
|
1ecd26adb5 | ||
|
|
6d9fc672d5 | ||
|
|
b9361b0868 | ||
|
|
e47f4ef602 | ||
|
|
961be7fd12 | ||
|
|
a5a21f47d1 | ||
|
|
a06cd84974 | ||
|
|
e3703b43c1 | ||
|
|
f6dc25c0ce |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.9.0
|
||||
PROJECT_NUMBER = 2025.9.2
|
||||
|
||||
# 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
|
||||
|
||||
@@ -212,7 +212,7 @@ def has_mqtt_logging() -> bool:
|
||||
if CONF_TOPIC not in log_topic:
|
||||
return False
|
||||
|
||||
return log_topic[CONF_LEVEL] != "NONE"
|
||||
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
||||
|
||||
|
||||
def has_mqtt() -> bool:
|
||||
|
||||
@@ -10,7 +10,8 @@ from esphome.const import (
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||
DEPENDENCIES = ["wifi"]
|
||||
@@ -40,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL)
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ using namespace bytebuffer;
|
||||
|
||||
static const char *const TAG = "esp32_improv.component";
|
||||
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
|
||||
static constexpr uint16_t STOP_ADVERTISING_DELAY =
|
||||
10000; // Delay (ms) before stopping service to allow BLE clients to read the final state
|
||||
|
||||
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
|
||||
|
||||
@@ -31,6 +33,9 @@ void ESP32ImprovComponent::setup() {
|
||||
#endif
|
||||
global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
|
||||
[this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
|
||||
|
||||
// Start with loop disabled - will be enabled by start() when needed
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::setup_characteristics() {
|
||||
@@ -190,6 +195,25 @@ void ESP32ImprovComponent::set_status_indicator_state_(bool state) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
const char *ESP32ImprovComponent::state_to_string_(improv::State state) {
|
||||
switch (state) {
|
||||
case improv::STATE_STOPPED:
|
||||
return "STOPPED";
|
||||
case improv::STATE_AWAITING_AUTHORIZATION:
|
||||
return "AWAITING_AUTHORIZATION";
|
||||
case improv::STATE_AUTHORIZED:
|
||||
return "AUTHORIZED";
|
||||
case improv::STATE_PROVISIONING:
|
||||
return "PROVISIONING";
|
||||
case improv::STATE_PROVISIONED:
|
||||
return "PROVISIONED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ESP32ImprovComponent::check_identify_() {
|
||||
uint32_t now = millis();
|
||||
|
||||
@@ -203,31 +227,42 @@ bool ESP32ImprovComponent::check_identify_() {
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::set_state_(improv::State state) {
|
||||
ESP_LOGV(TAG, "Setting state: %d", state);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
if (this->state_ != state) {
|
||||
ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_,
|
||||
this->state_to_string_(state), state);
|
||||
}
|
||||
#endif
|
||||
this->state_ = state;
|
||||
if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
|
||||
if (this->status_ != nullptr && (this->status_->get_value().empty() || this->status_->get_value()[0] != state)) {
|
||||
this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
|
||||
if (state != improv::STATE_STOPPED)
|
||||
this->status_->notify();
|
||||
}
|
||||
std::vector<uint8_t> service_data(8, 0);
|
||||
service_data[0] = 0x77; // PR
|
||||
service_data[1] = 0x46; // IM
|
||||
service_data[2] = static_cast<uint8_t>(state);
|
||||
// Only advertise valid Improv states (0x01-0x04).
|
||||
// STATE_STOPPED (0x00) is internal only and not part of the Improv spec.
|
||||
// Advertising 0x00 causes undefined behavior in some clients and makes them
|
||||
// repeatedly connect trying to determine the actual state.
|
||||
if (state != improv::STATE_STOPPED) {
|
||||
std::vector<uint8_t> service_data(8, 0);
|
||||
service_data[0] = 0x77; // PR
|
||||
service_data[1] = 0x46; // IM
|
||||
service_data[2] = static_cast<uint8_t>(state);
|
||||
|
||||
uint8_t capabilities = 0x00;
|
||||
uint8_t capabilities = 0x00;
|
||||
#ifdef USE_OUTPUT
|
||||
if (this->status_indicator_ != nullptr)
|
||||
capabilities |= improv::CAPABILITY_IDENTIFY;
|
||||
if (this->status_indicator_ != nullptr)
|
||||
capabilities |= improv::CAPABILITY_IDENTIFY;
|
||||
#endif
|
||||
|
||||
service_data[3] = capabilities;
|
||||
service_data[4] = 0x00; // Reserved
|
||||
service_data[5] = 0x00; // Reserved
|
||||
service_data[6] = 0x00; // Reserved
|
||||
service_data[7] = 0x00; // Reserved
|
||||
service_data[3] = capabilities;
|
||||
service_data[4] = 0x00; // Reserved
|
||||
service_data[5] = 0x00; // Reserved
|
||||
service_data[6] = 0x00; // Reserved
|
||||
service_data[7] = 0x00; // Reserved
|
||||
|
||||
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
||||
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
||||
}
|
||||
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||
this->state_callback_.call(this->state_, this->error_state_);
|
||||
#endif
|
||||
@@ -237,7 +272,12 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
|
||||
if (error != improv::ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Error: %d", error);
|
||||
}
|
||||
if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
|
||||
// The error_ characteristic is initialized in setup_characteristics() which is called
|
||||
// from the loop, while the BLE disconnect callback is registered in setup().
|
||||
// error_ can be nullptr if:
|
||||
// 1. A client connects/disconnects before setup_characteristics() is called
|
||||
// 2. The device is already provisioned so the service never starts (should_start_ is false)
|
||||
if (this->error_ != nullptr && (this->error_->get_value().empty() || this->error_->get_value()[0] != error)) {
|
||||
this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
|
||||
if (this->state_ != improv::STATE_STOPPED)
|
||||
this->error_->notify();
|
||||
@@ -261,7 +301,10 @@ void ESP32ImprovComponent::start() {
|
||||
|
||||
void ESP32ImprovComponent::stop() {
|
||||
this->should_start_ = false;
|
||||
this->set_timeout("end-service", 1000, [this] {
|
||||
// Wait before stopping the service to ensure all BLE clients see the state change.
|
||||
// This prevents clients from repeatedly reconnecting and wasting resources by allowing
|
||||
// them to observe that the device is provisioned before the service disappears.
|
||||
this->set_timeout("end-service", STOP_ADVERTISING_DELAY, [this] {
|
||||
if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
|
||||
return;
|
||||
this->service_->stop();
|
||||
|
||||
@@ -79,12 +79,12 @@ class ESP32ImprovComponent : public Component {
|
||||
std::vector<uint8_t> incoming_data_;
|
||||
wifi::WiFiAP connecting_sta_;
|
||||
|
||||
BLEService *service_ = nullptr;
|
||||
BLECharacteristic *status_;
|
||||
BLECharacteristic *error_;
|
||||
BLECharacteristic *rpc_;
|
||||
BLECharacteristic *rpc_response_;
|
||||
BLECharacteristic *capabilities_;
|
||||
BLEService *service_{nullptr};
|
||||
BLECharacteristic *status_{nullptr};
|
||||
BLECharacteristic *error_{nullptr};
|
||||
BLECharacteristic *rpc_{nullptr};
|
||||
BLECharacteristic *rpc_response_{nullptr};
|
||||
BLECharacteristic *capabilities_{nullptr};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *authorizer_{nullptr};
|
||||
@@ -108,6 +108,9 @@ class ESP32ImprovComponent : public Component {
|
||||
void process_incoming_data_();
|
||||
void on_wifi_connect_timeout_();
|
||||
bool check_identify_();
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
const char *state_to_string_(improv::State state);
|
||||
#endif
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -16,7 +16,8 @@ from esphome.const import (
|
||||
CONF_SAFE_MODE,
|
||||
CONF_VERSION,
|
||||
)
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
import esphome.final_validate as fv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -121,7 +122,7 @@ CONFIG_SCHEMA = (
|
||||
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.OTA_UPDATES)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace gpio {
|
||||
|
||||
static const char *const TAG = "gpio.binary_sensor";
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
static const LogString *interrupt_type_to_string(gpio::InterruptType type) {
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
@@ -22,6 +23,7 @@ static const LogString *interrupt_type_to_string(gpio::InterruptType type) {
|
||||
static const LogString *gpio_mode_to_string(bool use_interrupt) {
|
||||
return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling");
|
||||
}
|
||||
#endif
|
||||
|
||||
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||
bool new_state = arg->isr_pin_.digital_read();
|
||||
|
||||
@@ -128,4 +128,4 @@ async def to_code(config):
|
||||
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.37")
|
||||
if CORE.is_libretiny or CORE.is_esp32:
|
||||
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")
|
||||
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
||||
|
||||
@@ -3,7 +3,8 @@ import esphome.codegen as cg
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns
|
||||
|
||||
@@ -40,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.OTA_UPDATES)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
|
||||
@@ -11,7 +11,8 @@ from esphome.const import (
|
||||
CONF_SERVICES,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
@@ -72,7 +73,7 @@ def mdns_service(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.NETWORK_SERVICES)
|
||||
async def to_code(config):
|
||||
if config[CONF_DISABLED] is True:
|
||||
return
|
||||
|
||||
@@ -401,6 +401,12 @@ class DriverChip:
|
||||
sequence.append((MADCTL, madctl))
|
||||
return madctl
|
||||
|
||||
def skip_command(self, command: str):
|
||||
"""
|
||||
Allow suppressing a standard command in the init sequence.
|
||||
"""
|
||||
return self.get_default(f"no_{command.lower()}", False)
|
||||
|
||||
def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
|
||||
"""
|
||||
Create the init sequence for the display.
|
||||
@@ -432,7 +438,9 @@ class DriverChip:
|
||||
sequence.append((INVOFF,))
|
||||
if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)):
|
||||
sequence.append((BRIGHTNESS, brightness))
|
||||
sequence.append((SLPOUT,))
|
||||
# Add a SLPOUT command if required.
|
||||
if not self.skip_command("SLPOUT"):
|
||||
sequence.append((SLPOUT,))
|
||||
sequence.append((DISPON,))
|
||||
|
||||
# Flatten the sequence into a list of bytes, with the length of each command
|
||||
|
||||
@@ -7,6 +7,7 @@ wave_4_3 = DriverChip(
|
||||
"ESP32-S3-TOUCH-LCD-4.3",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
color_order="RGB",
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="16MHz",
|
||||
|
||||
@@ -27,7 +27,8 @@ DriverChip(
|
||||
bus_mode=TYPE_QUAD,
|
||||
brightness=0xD0,
|
||||
color_order=MODE_RGB,
|
||||
initsequence=(SLPOUT,), # Requires early SLPOUT
|
||||
no_slpout=True, # SLPOUT is in the init sequence, early
|
||||
initsequence=(SLPOUT,),
|
||||
)
|
||||
|
||||
DriverChip(
|
||||
@@ -95,6 +96,7 @@ CO5300 = DriverChip(
|
||||
brightness=0xD0,
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
no_slpout=True,
|
||||
initsequence=(
|
||||
(SLPOUT,), # Requires early SLPOUT
|
||||
(PAGESEL, 0x00),
|
||||
|
||||
@@ -10,7 +10,8 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["md5", "safe_mode"]
|
||||
@@ -82,7 +83,7 @@ BASE_OTA_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.OTA_UPDATES)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_OTA")
|
||||
|
||||
|
||||
@@ -121,15 +121,11 @@ def transport_schema(cls):
|
||||
return TRANSPORT_SCHEMA.extend({cv.GenerateID(): cv.declare_id(cls)})
|
||||
|
||||
|
||||
# Build a list of sensors for this platform
|
||||
CORE.data[DOMAIN] = {CONF_SENSORS: []}
|
||||
|
||||
|
||||
def get_sensors(transport_id):
|
||||
"""Return the list of sensors for this platform."""
|
||||
return (
|
||||
sensor
|
||||
for sensor in CORE.data[DOMAIN][CONF_SENSORS]
|
||||
for sensor in CORE.data.setdefault(DOMAIN, {}).setdefault(CONF_SENSORS, [])
|
||||
if sensor[CONF_TRANSPORT_ID] == transport_id
|
||||
)
|
||||
|
||||
@@ -137,7 +133,8 @@ def get_sensors(transport_id):
|
||||
def validate_packet_transport_sensor(config):
|
||||
if CONF_NAME in config and CONF_INTERNAL not in config:
|
||||
raise cv.Invalid("Must provide internal: config when using name:")
|
||||
CORE.data[DOMAIN][CONF_SENSORS].append(config)
|
||||
conf_sensors = CORE.data.setdefault(DOMAIN, {}).setdefault(CONF_SENSORS, [])
|
||||
conf_sensors.append(config)
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ void SX126x::configure() {
|
||||
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
|
||||
|
||||
// set packet params and sync word
|
||||
this->set_packet_params_(this->payload_length_);
|
||||
this->set_packet_params_(this->get_max_packet_size());
|
||||
if (this->sync_value_.size() == 2) {
|
||||
this->write_register_(REG_LORA_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
||||
}
|
||||
@@ -236,7 +236,7 @@ void SX126x::configure() {
|
||||
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
|
||||
|
||||
// set packet params and sync word
|
||||
this->set_packet_params_(this->payload_length_);
|
||||
this->set_packet_params_(this->get_max_packet_size());
|
||||
if (!this->sync_value_.empty()) {
|
||||
this->write_register_(REG_GFSK_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
||||
}
|
||||
@@ -274,7 +274,7 @@ void SX126x::set_packet_params_(uint8_t payload_length) {
|
||||
buf[2] = (this->preamble_detect_ > 0) ? ((this->preamble_detect_ - 1) | 0x04) : 0x00;
|
||||
buf[3] = this->sync_value_.size() * 8;
|
||||
buf[4] = 0x00;
|
||||
buf[5] = 0x00;
|
||||
buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
|
||||
buf[6] = payload_length;
|
||||
buf[7] = this->crc_enable_ ? 0x06 : 0x01;
|
||||
buf[8] = 0x00;
|
||||
@@ -314,6 +314,9 @@ SX126xError SX126x::transmit_packet(const std::vector<uint8_t> &packet) {
|
||||
buf[0] = 0xFF;
|
||||
buf[1] = 0xFF;
|
||||
this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2);
|
||||
if (this->payload_length_ == 0) {
|
||||
this->set_packet_params_(this->get_max_packet_size());
|
||||
}
|
||||
if (this->rx_start_) {
|
||||
this->set_mode_rx();
|
||||
} else {
|
||||
|
||||
@@ -72,6 +72,7 @@ void USBUartTypeCH34X::enable_channels() {
|
||||
if (channel->index_ >= 2)
|
||||
cmd += 0xE;
|
||||
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor, callback);
|
||||
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd + 3, 0x80, 0, callback);
|
||||
}
|
||||
USBUartTypeCdcAcm::enable_channels();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ from esphome.components.esp32 import add_idf_component
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network", "web_server_base"]
|
||||
@@ -22,7 +23,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.WEB_SERVER_OTA)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
@@ -26,7 +27,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
@coroutine_with_priority(CoroPriority.WEB_SERVER_BASE)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@@ -39,5 +40,7 @@ async def to_code(config):
|
||||
cg.add_library("Update", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266WiFi", None)
|
||||
if CORE.is_libretiny:
|
||||
CORE.add_platformio_option("lib_ignore", ["ESPAsyncTCP", "RPAsyncTCP"])
|
||||
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10")
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.9.0"
|
||||
__version__ = "2025.9.2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -396,7 +396,7 @@ async def add_includes(includes):
|
||||
async def _add_platformio_options(pio_options):
|
||||
# Add includes at the very end, so that they override everything
|
||||
for key, val in pio_options.items():
|
||||
if key == "build_flags" and not isinstance(val, list):
|
||||
if key in ["build_flags", "lib_ignore"] and not isinstance(val, list):
|
||||
val = [val]
|
||||
cg.add_platformio_option(key, val)
|
||||
|
||||
|
||||
@@ -90,11 +90,30 @@ class CoroPriority(enum.IntEnum):
|
||||
# Examples: status_led (80)
|
||||
STATUS = 80
|
||||
|
||||
# Web server infrastructure
|
||||
# Examples: web_server_base (65)
|
||||
WEB_SERVER_BASE = 65
|
||||
|
||||
# Network portal services
|
||||
# Examples: captive_portal (64)
|
||||
CAPTIVE_PORTAL = 64
|
||||
|
||||
# Communication protocols and services
|
||||
# Examples: web_server_base (65), captive_portal (64), wifi (60), ethernet (60),
|
||||
# mdns (55), ota_updates (54), web_server_ota (52)
|
||||
# Examples: wifi (60), ethernet (60)
|
||||
COMMUNICATION = 60
|
||||
|
||||
# Network discovery and management services
|
||||
# Examples: mdns (55)
|
||||
NETWORK_SERVICES = 55
|
||||
|
||||
# OTA update services
|
||||
# Examples: ota_updates (54)
|
||||
OTA_UPDATES = 54
|
||||
|
||||
# Web-based OTA services
|
||||
# Examples: web_server_ota (52)
|
||||
WEB_SERVER_OTA = 52
|
||||
|
||||
# Application-level services
|
||||
# Examples: safe_mode (50)
|
||||
APPLICATION = 50
|
||||
|
||||
42
tests/components/mdns/test-comprehensive.esp8266-ard.yaml
Normal file
42
tests/components/mdns/test-comprehensive.esp8266-ard.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
# Comprehensive ESP8266 test for mdns with multiple network components
|
||||
# Tests the complete priority chain:
|
||||
# wifi (60) -> mdns (55) -> ota (54) -> web_server_ota (52)
|
||||
|
||||
esphome:
|
||||
name: mdns-comprehensive-test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
# web_server_base should run at priority 65 (before wifi)
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
# mdns should run at priority 55 (after wifi at 60)
|
||||
mdns:
|
||||
services:
|
||||
- service: _http
|
||||
protocol: _tcp
|
||||
port: 80
|
||||
|
||||
# OTA should run at priority 54 (after mdns)
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "otapassword"
|
||||
|
||||
# Test status LED at priority 80
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO2
|
||||
inverted: true
|
||||
|
||||
# Include API at priority 40
|
||||
api:
|
||||
password: "apipassword"
|
||||
@@ -13,7 +13,12 @@ def test_coro_priority_enum_values() -> None:
|
||||
assert CoroPriority.CORE == 100
|
||||
assert CoroPriority.DIAGNOSTICS == 90
|
||||
assert CoroPriority.STATUS == 80
|
||||
assert CoroPriority.WEB_SERVER_BASE == 65
|
||||
assert CoroPriority.CAPTIVE_PORTAL == 64
|
||||
assert CoroPriority.COMMUNICATION == 60
|
||||
assert CoroPriority.NETWORK_SERVICES == 55
|
||||
assert CoroPriority.OTA_UPDATES == 54
|
||||
assert CoroPriority.WEB_SERVER_OTA == 52
|
||||
assert CoroPriority.APPLICATION == 50
|
||||
assert CoroPriority.WEB == 40
|
||||
assert CoroPriority.AUTOMATION == 30
|
||||
@@ -70,7 +75,12 @@ def test_float_and_enum_are_interchangeable() -> None:
|
||||
(CoroPriority.CORE, 100.0),
|
||||
(CoroPriority.DIAGNOSTICS, 90.0),
|
||||
(CoroPriority.STATUS, 80.0),
|
||||
(CoroPriority.WEB_SERVER_BASE, 65.0),
|
||||
(CoroPriority.CAPTIVE_PORTAL, 64.0),
|
||||
(CoroPriority.COMMUNICATION, 60.0),
|
||||
(CoroPriority.NETWORK_SERVICES, 55.0),
|
||||
(CoroPriority.OTA_UPDATES, 54.0),
|
||||
(CoroPriority.WEB_SERVER_OTA, 52.0),
|
||||
(CoroPriority.APPLICATION, 50.0),
|
||||
(CoroPriority.WEB, 40.0),
|
||||
(CoroPriority.AUTOMATION, 30.0),
|
||||
@@ -164,8 +174,13 @@ def test_enum_priority_comparison() -> None:
|
||||
assert CoroPriority.NETWORK_TRANSPORT > CoroPriority.CORE
|
||||
assert CoroPriority.CORE > CoroPriority.DIAGNOSTICS
|
||||
assert CoroPriority.DIAGNOSTICS > CoroPriority.STATUS
|
||||
assert CoroPriority.STATUS > CoroPriority.COMMUNICATION
|
||||
assert CoroPriority.COMMUNICATION > CoroPriority.APPLICATION
|
||||
assert CoroPriority.STATUS > CoroPriority.WEB_SERVER_BASE
|
||||
assert CoroPriority.WEB_SERVER_BASE > CoroPriority.CAPTIVE_PORTAL
|
||||
assert CoroPriority.CAPTIVE_PORTAL > CoroPriority.COMMUNICATION
|
||||
assert CoroPriority.COMMUNICATION > CoroPriority.NETWORK_SERVICES
|
||||
assert CoroPriority.NETWORK_SERVICES > CoroPriority.OTA_UPDATES
|
||||
assert CoroPriority.OTA_UPDATES > CoroPriority.WEB_SERVER_OTA
|
||||
assert CoroPriority.WEB_SERVER_OTA > CoroPriority.APPLICATION
|
||||
assert CoroPriority.APPLICATION > CoroPriority.WEB
|
||||
assert CoroPriority.WEB > CoroPriority.AUTOMATION
|
||||
assert CoroPriority.AUTOMATION > CoroPriority.BUS
|
||||
|
||||
@@ -1226,6 +1226,18 @@ def test_has_mqtt_logging_no_log_topic() -> None:
|
||||
setup_core(config={})
|
||||
assert has_mqtt_logging() is False
|
||||
|
||||
# Setup MQTT config with CONF_LOG_TOPIC but no CONF_LEVEL (regression test for #10771)
|
||||
# This simulates the default configuration created by validate_config in the MQTT component
|
||||
setup_core(
|
||||
config={
|
||||
CONF_MQTT: {
|
||||
CONF_BROKER: "mqtt.local",
|
||||
CONF_LOG_TOPIC: {CONF_TOPIC: "esphome/debug"},
|
||||
}
|
||||
}
|
||||
)
|
||||
assert has_mqtt_logging() is True
|
||||
|
||||
|
||||
def test_has_mqtt() -> None:
|
||||
"""Test has_mqtt function."""
|
||||
|
||||
Reference in New Issue
Block a user