1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-01 09:32:21 +01:00

Merge pull request #10932 from esphome/bump-2025.9.2

2025.9.2
This commit is contained in:
Jesse Hills
2025-09-30 07:50:17 +13:00
committed by GitHub
12 changed files with 95 additions and 32 deletions

View File

@@ -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.9.1 PROJECT_NUMBER = 2025.9.2
# 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

View File

@@ -15,6 +15,8 @@ using namespace bytebuffer;
static const char *const TAG = "esp32_improv.component"; 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 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; } ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
@@ -31,6 +33,9 @@ void ESP32ImprovComponent::setup() {
#endif #endif
global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
[this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); [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() { void ESP32ImprovComponent::setup_characteristics() {
@@ -190,6 +195,25 @@ void ESP32ImprovComponent::set_status_indicator_state_(bool state) {
#endif #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_() { bool ESP32ImprovComponent::check_identify_() {
uint32_t now = millis(); uint32_t now = millis();
@@ -203,31 +227,42 @@ bool ESP32ImprovComponent::check_identify_() {
} }
void ESP32ImprovComponent::set_state_(improv::State state) { 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; 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))); this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
if (state != improv::STATE_STOPPED) if (state != improv::STATE_STOPPED)
this->status_->notify(); this->status_->notify();
} }
std::vector<uint8_t> service_data(8, 0); // Only advertise valid Improv states (0x01-0x04).
service_data[0] = 0x77; // PR // STATE_STOPPED (0x00) is internal only and not part of the Improv spec.
service_data[1] = 0x46; // IM // Advertising 0x00 causes undefined behavior in some clients and makes them
service_data[2] = static_cast<uint8_t>(state); // 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 #ifdef USE_OUTPUT
if (this->status_indicator_ != nullptr) if (this->status_indicator_ != nullptr)
capabilities |= improv::CAPABILITY_IDENTIFY; capabilities |= improv::CAPABILITY_IDENTIFY;
#endif #endif
service_data[3] = capabilities; service_data[3] = capabilities;
service_data[4] = 0x00; // Reserved service_data[4] = 0x00; // Reserved
service_data[5] = 0x00; // Reserved service_data[5] = 0x00; // Reserved
service_data[6] = 0x00; // Reserved service_data[6] = 0x00; // Reserved
service_data[7] = 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 #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
this->state_callback_.call(this->state_, this->error_state_); this->state_callback_.call(this->state_, this->error_state_);
#endif #endif
@@ -237,7 +272,12 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
if (error != improv::ERROR_NONE) { if (error != improv::ERROR_NONE) {
ESP_LOGE(TAG, "Error: %d", error); 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))); this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
if (this->state_ != improv::STATE_STOPPED) if (this->state_ != improv::STATE_STOPPED)
this->error_->notify(); this->error_->notify();
@@ -261,7 +301,10 @@ void ESP32ImprovComponent::start() {
void ESP32ImprovComponent::stop() { void ESP32ImprovComponent::stop() {
this->should_start_ = false; 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) if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
return; return;
this->service_->stop(); this->service_->stop();

View File

@@ -79,12 +79,12 @@ class ESP32ImprovComponent : public Component {
std::vector<uint8_t> incoming_data_; std::vector<uint8_t> incoming_data_;
wifi::WiFiAP connecting_sta_; wifi::WiFiAP connecting_sta_;
BLEService *service_ = nullptr; BLEService *service_{nullptr};
BLECharacteristic *status_; BLECharacteristic *status_{nullptr};
BLECharacteristic *error_; BLECharacteristic *error_{nullptr};
BLECharacteristic *rpc_; BLECharacteristic *rpc_{nullptr};
BLECharacteristic *rpc_response_; BLECharacteristic *rpc_response_{nullptr};
BLECharacteristic *capabilities_; BLECharacteristic *capabilities_{nullptr};
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *authorizer_{nullptr}; binary_sensor::BinarySensor *authorizer_{nullptr};
@@ -108,6 +108,9 @@ class ESP32ImprovComponent : public Component {
void process_incoming_data_(); void process_incoming_data_();
void on_wifi_connect_timeout_(); void on_wifi_connect_timeout_();
bool check_identify_(); 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) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -128,4 +128,4 @@ async def to_code(config):
cg.add_library("tonia/HeatpumpIR", "1.0.37") cg.add_library("tonia/HeatpumpIR", "1.0.37")
if CORE.is_libretiny or CORE.is_esp32: if CORE.is_libretiny or CORE.is_esp32:
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])

View File

@@ -401,6 +401,12 @@ class DriverChip:
sequence.append((MADCTL, madctl)) sequence.append((MADCTL, madctl))
return 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]: def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
""" """
Create the init sequence for the display. Create the init sequence for the display.
@@ -432,7 +438,9 @@ class DriverChip:
sequence.append((INVOFF,)) sequence.append((INVOFF,))
if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)): if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)):
sequence.append((BRIGHTNESS, 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,)) sequence.append((DISPON,))
# Flatten the sequence into a list of bytes, with the length of each command # Flatten the sequence into a list of bytes, with the length of each command

View File

@@ -7,6 +7,7 @@ wave_4_3 = DriverChip(
"ESP32-S3-TOUCH-LCD-4.3", "ESP32-S3-TOUCH-LCD-4.3",
swap_xy=UNDEFINED, swap_xy=UNDEFINED,
initsequence=(), initsequence=(),
color_order="RGB",
width=800, width=800,
height=480, height=480,
pclk_frequency="16MHz", pclk_frequency="16MHz",

View File

@@ -27,7 +27,8 @@ DriverChip(
bus_mode=TYPE_QUAD, bus_mode=TYPE_QUAD,
brightness=0xD0, brightness=0xD0,
color_order=MODE_RGB, color_order=MODE_RGB,
initsequence=(SLPOUT,), # Requires early SLPOUT no_slpout=True, # SLPOUT is in the init sequence, early
initsequence=(SLPOUT,),
) )
DriverChip( DriverChip(
@@ -95,6 +96,7 @@ CO5300 = DriverChip(
brightness=0xD0, brightness=0xD0,
color_order=MODE_RGB, color_order=MODE_RGB,
bus_mode=TYPE_QUAD, bus_mode=TYPE_QUAD,
no_slpout=True,
initsequence=( initsequence=(
(SLPOUT,), # Requires early SLPOUT (SLPOUT,), # Requires early SLPOUT
(PAGESEL, 0x00), (PAGESEL, 0x00),

View File

@@ -217,7 +217,7 @@ void SX126x::configure() {
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4); this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
// set packet params and sync word // 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) { if (this->sync_value_.size() == 2) {
this->write_register_(REG_LORA_SYNCWORD, this->sync_value_.data(), this->sync_value_.size()); 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); this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
// set packet params and sync word // 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()) { if (!this->sync_value_.empty()) {
this->write_register_(REG_GFSK_SYNCWORD, this->sync_value_.data(), this->sync_value_.size()); 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[2] = (this->preamble_detect_ > 0) ? ((this->preamble_detect_ - 1) | 0x04) : 0x00;
buf[3] = this->sync_value_.size() * 8; buf[3] = this->sync_value_.size() * 8;
buf[4] = 0x00; buf[4] = 0x00;
buf[5] = 0x00; buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
buf[6] = payload_length; buf[6] = payload_length;
buf[7] = this->crc_enable_ ? 0x06 : 0x01; buf[7] = this->crc_enable_ ? 0x06 : 0x01;
buf[8] = 0x00; buf[8] = 0x00;
@@ -314,6 +314,9 @@ SX126xError SX126x::transmit_packet(const std::vector<uint8_t> &packet) {
buf[0] = 0xFF; buf[0] = 0xFF;
buf[1] = 0xFF; buf[1] = 0xFF;
this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2); 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_) { if (this->rx_start_) {
this->set_mode_rx(); this->set_mode_rx();
} else { } else {

View File

@@ -72,6 +72,7 @@ void USBUartTypeCH34X::enable_channels() {
if (channel->index_ >= 2) if (channel->index_ >= 2)
cmd += 0xE; 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, value, (factor << 8) | divisor, callback);
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd + 3, 0x80, 0, callback);
} }
USBUartTypeCdcAcm::enable_channels(); USBUartTypeCdcAcm::enable_channels();
} }

View File

@@ -40,5 +40,7 @@ async def to_code(config):
cg.add_library("Update", None) cg.add_library("Update", None)
if CORE.is_esp8266: if CORE.is_esp8266:
cg.add_library("ESP8266WiFi", None) 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 # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10") cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10")

View File

@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum from esphome.enum import StrEnum
__version__ = "2025.9.1" __version__ = "2025.9.2"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -396,7 +396,7 @@ async def add_includes(includes):
async def _add_platformio_options(pio_options): async def _add_platformio_options(pio_options):
# Add includes at the very end, so that they override everything # Add includes at the very end, so that they override everything
for key, val in pio_options.items(): 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] val = [val]
cg.add_platformio_option(key, val) cg.add_platformio_option(key, val)