Merge remote-tracking branch 'upstream/dev' into get_peername_stack_save_ram

# Conflicts:
#	esphome/components/voice_assistant/voice_assistant.cpp
This commit is contained in:
J. Nick Koston
2026-01-04 13:05:42 -10:00
47 changed files with 662 additions and 251 deletions

View File

@@ -22,7 +22,6 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
DOMAIN = "esp32_ble"

View File

@@ -95,11 +95,13 @@ void GCJA5Component::parse_data_() {
if (!this->first_status_log_) {
this->first_status_log_ = true;
ESP_LOGI(TAG, "GCJA5 Status");
ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03);
ESP_LOGI(TAG, "PD Status : %i", (status >> 4) & 0x03);
ESP_LOGI(TAG, "LD Status : %i", (status >> 2) & 0x03);
ESP_LOGI(TAG, "Fan Status : %i", (status >> 0) & 0x03);
ESP_LOGI(TAG,
"GCJA5 Status\n"
"Overall Status : %i\n"
"PD Status : %i\n"
"LD Status : %i\n"
"Fan Status : %i",
(status >> 6) & 0x03, (status >> 4) & 0x03, (status >> 2) & 0x03, (status >> 0) & 0x03);
}
}

View File

@@ -27,8 +27,10 @@ void GLR01I2CComponent::setup() {
}
void GLR01I2CComponent::dump_config() {
ESP_LOGCONFIG(TAG, "GL-R01 I2C:");
ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_);
ESP_LOGCONFIG(TAG,
"GL-R01 I2C:\n"
" Firmware Version: 0x%04X",
this->version_);
LOG_I2C_DEVICE(this);
LOG_SENSOR(" ", "Distance", this);
}

View File

@@ -89,11 +89,12 @@ void HC8Component::calibrate(uint16_t baseline) {
float HC8Component::get_setup_priority() const { return setup_priority::DATA; }
void HC8Component::dump_config() {
ESP_LOGCONFIG(TAG, "HC8:");
ESP_LOGCONFIG(TAG,
"HC8:\n"
" Warmup time: %" PRIu32 " s",
this->warmup_seconds_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
this->check_uart_settings(9600);
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
}
} // namespace esphome::hc8

View File

@@ -33,15 +33,15 @@ void HLW8012Component::setup() {
}
}
void HLW8012Component::dump_config() {
ESP_LOGCONFIG(TAG, "HLW8012:");
LOG_PIN(" SEL Pin: ", this->sel_pin_);
LOG_PIN(" CF Pin: ", this->cf_pin_);
LOG_PIN(" CF1 Pin: ", this->cf1_pin_);
ESP_LOGCONFIG(TAG,
"HLW8012:\n"
" Change measurement mode every %" PRIu32 "\n"
" Current resistor: %.1f mΩ\n"
" Voltage Divider: %.1f",
this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_);
LOG_PIN(" SEL Pin: ", this->sel_pin_);
LOG_PIN(" CF Pin: ", this->cf_pin_);
LOG_PIN(" CF1 Pin: ", this->cf1_pin_);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);

View File

@@ -35,8 +35,10 @@ uint8_t HONEYWELLABPSensor::readsensor_() {
pressure_count_ = ((uint16_t) (buf_[0]) << 8 & 0x3F00) | ((uint16_t) (buf_[1]) & 0xFF);
// 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3
temperature_count_ = (((uint16_t) (buf_[2]) << 3) & 0x7F8) | (((uint16_t) (buf_[3]) >> 5) & 0x7);
ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_);
ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_);
ESP_LOGV(TAG,
"Sensor pressure_count_ %d\n"
"Sensor temperature_count_ %d",
pressure_count_, temperature_count_);
}
return status_;
}

View File

@@ -61,13 +61,13 @@ void INA260Component::setup() {
}
void INA260Component::dump_config() {
ESP_LOGCONFIG(TAG, "INA260:");
ESP_LOGCONFIG(TAG,
"INA260:\n"
" Manufacture ID: 0x%x\n"
" Device ID: 0x%x",
this->manufacture_id_, this->device_id_);
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->manufacture_id_);
ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_);
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);

View File

@@ -364,8 +364,10 @@ bool INA2XX::configure_shunt_() {
ESP_LOGW(TAG, "Shunt value too high");
}
this->shunt_cal_ &= 0x7FFF;
ESP_LOGV(TAG, "Given Rshunt=%f Ohm and Max_current=%.3f", this->shunt_resistance_ohm_, this->max_current_a_);
ESP_LOGV(TAG, "New CURRENT_LSB=%f, SHUNT_CAL=%u", this->current_lsb_, this->shunt_cal_);
ESP_LOGV(TAG,
"Given Rshunt=%f Ohm and Max_current=%.3f\n"
"New CURRENT_LSB=%f, SHUNT_CAL=%u",
this->shunt_resistance_ohm_, this->max_current_a_, this->current_lsb_, this->shunt_cal_);
return this->write_unsigned_16_(RegisterMap::REG_SHUNT_CAL, this->shunt_cal_);
}

View File

@@ -146,19 +146,14 @@ void Lc709203f::update() {
}
void Lc709203f::dump_config() {
ESP_LOGCONFIG(TAG, "LC709203F:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG,
"LC709203F:\n"
" Pack Size: %d mAH\n"
" Pack APA: 0x%02X",
this->pack_size_, this->apa_);
// This is only true if the pack_voltage_ is either 0x0000 or 0x0001. The config validator
// should have already verified this.
ESP_LOGCONFIG(TAG, " Pack Rated Voltage: 3.%sV", this->pack_voltage_ == 0x0000 ? "8" : "7");
" Pack APA: 0x%02X\n"
" Pack Rated Voltage: 3.%sV",
this->pack_size_, this->apa_, this->pack_voltage_ == 0x0000 ? "8" : "7");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Battery Remaining", this->battery_remaining_sensor_);

View File

@@ -130,8 +130,10 @@ void LEDCOutput::setup() {
}
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_);
ESP_LOGV(TAG, "Angle of %.1f° results in hpoint %u", this->phase_angle_, hpoint);
ESP_LOGV(TAG,
"Configured frequency %f with a bit depth of %u bits\n"
"Angle of %.1f° results in hpoint %u",
this->frequency_, this->bit_depth_, this->phase_angle_, hpoint);
ledc_channel_config_t chan_conf{};
chan_conf.gpio_num = this->pin_->get_pin();
@@ -147,25 +149,30 @@ void LEDCOutput::setup() {
}
void LEDCOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Output:");
LOG_PIN(" Pin ", this->pin_);
ESP_LOGCONFIG(TAG,
"Output:\n"
" Channel: %u\n"
" PWM Frequency: %.1f Hz\n"
" Phase angle: %.1f°\n"
" Bit depth: %u",
this->channel_, this->frequency_, this->phase_angle_, this->bit_depth_);
ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_));
ESP_LOGV(TAG, " Min frequency for bit depth: %f",
ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)));
ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1));
ESP_LOGV(TAG, " Min frequency for bit depth-1: %f",
ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)));
ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1));
ESP_LOGV(TAG, " Min frequency for bit depth+1: %f",
ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)));
ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS);
ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY);
LOG_PIN(" Pin ", this->pin_);
ESP_LOGV(TAG,
" Max frequency for bit depth: %f\n"
" Min frequency for bit depth: %f\n"
" Max frequency for bit depth-1: %f\n"
" Min frequency for bit depth-1: %f\n"
" Max frequency for bit depth+1: %f\n"
" Min frequency for bit depth+1: %f\n"
" Max res bits: %d\n"
" Clock frequency: %f",
ledc_max_frequency_for_bit_depth(this->bit_depth_),
ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)),
ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1),
ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)),
ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1),
ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)), MAX_RES_BITS,
CLOCK_FREQUENCY);
}
void LEDCOutput::update_frequency(float frequency) {

View File

@@ -31,9 +31,11 @@ void LibreTinyPWM::setup() {
}
void LibreTinyPWM::dump_config() {
ESP_LOGCONFIG(TAG, "PWM Output:");
ESP_LOGCONFIG(TAG,
"PWM Output:\n"
" Frequency: %.1f Hz",
this->frequency_);
LOG_PIN(" Pin ", this->pin_);
ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_);
}
void LibreTinyPWM::update_frequency(float frequency) {

View File

@@ -26,9 +26,11 @@ void M5Stack8AngleComponent::setup() {
}
void M5Stack8AngleComponent::dump_config() {
ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:");
ESP_LOGCONFIG(TAG,
"M5STACK_8ANGLE:\n"
" Firmware version: %d",
this->fw_version_);
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Firmware version: %d ", this->fw_version_);
}
float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) {

View File

@@ -11,9 +11,11 @@ float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; }
void MCP3204::setup() { this->spi_setup(); }
void MCP3204::dump_config() {
ESP_LOGCONFIG(TAG, "MCP3204:");
ESP_LOGCONFIG(TAG,
"MCP3204:\n"
" Reference Voltage: %.2fV",
this->reference_voltage_);
LOG_PIN(" CS Pin:", this->cs_);
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
}
float MCP3204::read_data(uint8_t pin, bool differential) {

View File

@@ -11,8 +11,10 @@ float MCP3204Sensor::get_setup_priority() const { return setup_priority::DATA; }
void MCP3204Sensor::dump_config() {
LOG_SENSOR("", "MCP3204 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
ESP_LOGCONFIG(TAG, " Differential Mode: %s", YESNO(this->differential_mode_));
ESP_LOGCONFIG(TAG,
" Pin: %u\n"
" Differential Mode: %s",
this->pin_, YESNO(this->differential_mode_));
LOG_UPDATE_INTERVAL(this);
}
float MCP3204Sensor::sample() { return this->parent_->read_data(this->pin_, this->differential_mode_); }

View File

@@ -63,12 +63,12 @@ void MCP9600Component::setup() {
}
void MCP9600Component::dump_config() {
ESP_LOGCONFIG(TAG, "MCP9600:");
ESP_LOGCONFIG(TAG,
"MCP9600:\n"
" Device ID: 0x%x",
this->device_id_);
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Device ID: 0x%x", this->device_id_);
LOG_SENSOR(" ", "Hot Junction Temperature", this->hot_junction_sensor_);
LOG_SENSOR(" ", "Cold Junction Temperature", this->cold_junction_sensor_);

View File

@@ -196,12 +196,12 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
}
void Modbus::dump_config() {
ESP_LOGCONFIG(TAG, "Modbus:");
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
ESP_LOGCONFIG(TAG,
"Modbus:\n"
" Send Wait Time: %d ms\n"
" CRC Disabled: %s",
this->send_wait_time_, YESNO(this->disable_crc_));
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
}
float Modbus::get_setup_priority() const {
// After UART bus

View File

@@ -17,10 +17,12 @@ static const uint16_t MANUFACTURER_ID = 0x000D;
static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32;
void MopekaStdCheck::dump_config() {
ESP_LOGCONFIG(TAG, "Mopeka Std Check");
ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100);
ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_);
ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_);
ESP_LOGCONFIG(TAG,
"Mopeka Std Check\n"
" Propane Butane mix: %.0f%%\n"
" Tank distance empty: %" PRIi32 "mm\n"
" Tank distance full: %" PRIi32 "mm",
this->propane_butane_mix_ * 100, this->empty_mm_, this->full_mm_);
LOG_SENSOR(" ", "Level", this->level_);
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@@ -166,10 +166,12 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
if (event.error_handle.error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle.esp_tls_last_esp_err);
ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle.esp_tls_stack_err);
ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle.esp_transport_sock_errno,
strerror(event.error_handle.esp_transport_sock_errno));
ESP_LOGE(TAG,
"Last error code reported from esp-tls: 0x%x\n"
"Last tls stack error number: 0x%x\n"
"Last captured errno : %d (%s)",
event.error_handle.esp_tls_last_esp_err, event.error_handle.esp_tls_stack_err,
event.error_handle.esp_transport_sock_errno, strerror(event.error_handle.esp_transport_sock_errno));
} else if (event.error_handle.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle.connect_return_code);
} else {

View File

@@ -154,15 +154,17 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes
void MQTTClientComponent::dump_config() {
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
// clang-format off
ESP_LOGCONFIG(TAG,
"MQTT:\n"
" Server Address: %s:%u (%s)\n"
" Username: " LOG_SECRET("'%s'") "\n"
" Client ID: " LOG_SECRET("'%s'") "\n"
" Clean Session: %s",
" Client ID: " LOG_SECRET("'%s'") "\n"
" Clean Session: %s",
this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf),
this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
YESNO(this->credentials_.clean_session));
// clang-format on
if (this->is_discovery_ip_enabled()) {
ESP_LOGCONFIG(TAG, " Discovery IP enabled");
}

View File

@@ -58,14 +58,14 @@ void MY9231OutputComponent::setup() {
}
}
void MY9231OutputComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MY9231:");
LOG_PIN(" DI Pin: ", this->pin_di_);
LOG_PIN(" DCKI Pin: ", this->pin_dcki_);
ESP_LOGCONFIG(TAG,
"MY9231:\n"
" Total number of channels: %u\n"
" Number of chips: %u\n"
" Bit depth: %u",
this->num_channels_, this->num_chips_, this->bit_depth_);
LOG_PIN(" DI Pin: ", this->pin_di_);
LOG_PIN(" DCKI Pin: ", this->pin_dcki_);
}
void MY9231OutputComponent::loop() {
if (!this->update_)

View File

@@ -395,10 +395,8 @@ void OpenthermHub::dump_config() {
this->write_initial_messages_(initial_messages);
this->write_repeating_messages_(repeating_messages);
ESP_LOGCONFIG(TAG, "OpenTherm:");
LOG_PIN(" In: ", this->in_pin_);
LOG_PIN(" Out: ", this->out_pin_);
ESP_LOGCONFIG(TAG,
"OpenTherm:\n"
" Sync mode: %s\n"
" Sensors: %s\n"
" Binary sensors: %s\n"
@@ -409,6 +407,8 @@ void OpenthermHub::dump_config() {
YESNO(this->sync_mode_), SHOW(OPENTHERM_SENSOR_LIST(ID, )), SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )),
SHOW(OPENTHERM_SWITCH_LIST(ID, )), SHOW(OPENTHERM_INPUT_SENSOR_LIST(ID, )),
SHOW(OPENTHERM_OUTPUT_LIST(ID, )), SHOW(OPENTHERM_NUMBER_LIST(ID, )));
LOG_PIN(" In: ", this->in_pin_);
LOG_PIN(" Out: ", this->out_pin_);
ESP_LOGCONFIG(TAG, " Initial requests:");
for (auto type : initial_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));

View File

@@ -126,9 +126,12 @@ void OpenThreadComponent::ot_main() {
ESP_LOGE(TAG, "Failed to set OpenThread linkmode.");
}
link_mode_config = otThreadGetLinkMode(esp_openthread_get_instance());
ESP_LOGD(TAG, "Link Mode Device Type: %s", link_mode_config.mDeviceType ? "true" : "false");
ESP_LOGD(TAG, "Link Mode Network Data: %s", link_mode_config.mNetworkData ? "true" : "false");
ESP_LOGD(TAG, "Link Mode RX On When Idle: %s", link_mode_config.mRxOnWhenIdle ? "true" : "false");
ESP_LOGD(TAG,
"Link Mode Device Type: %s\n"
"Link Mode Network Data: %s\n"
"Link Mode RX On When Idle: %s",
link_mode_config.mDeviceType ? "true" : "false", link_mode_config.mNetworkData ? "true" : "false",
link_mode_config.mRxOnWhenIdle ? "true" : "false");
// Run the main loop
#if CONFIG_OPENTHREAD_CLI
@@ -144,8 +147,8 @@ void OpenThreadComponent::ot_main() {
// Make sure the length is 0 so we fallback to the configuration
dataset.mLength = 0;
} else {
ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration");
ESP_LOGI(TAG, "(set force_dataset: true to override)");
ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration\n"
"(set force_dataset: true to override)");
}
#endif

View File

@@ -47,6 +47,8 @@ def require_wake_loop_threadsafe() -> None:
This enables the shared UDP loopback socket mechanism (~208 bytes RAM).
The socket is shared across all components that use this feature.
This call is a no-op if networking is not enabled in the configuration.
IMPORTANT: This is for background thread context only, NOT ISR context.
Socket operations are not safe to call from ISR handlers.
@@ -56,8 +58,11 @@ def require_wake_loop_threadsafe() -> None:
async def to_code(config):
socket.require_wake_loop_threadsafe()
"""
# Only set up once (idempotent - multiple components can call this)
if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False):
if CORE.has_networking and not CORE.data.get(
KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False
):
CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
cg.add_define("USE_WAKE_LOOP_THREADSAFE")
# Consume 1 socket for the shared wake notification socket

View File

@@ -172,21 +172,25 @@ struct SunAtTime {
void debug() const {
// debug output like in example 25.a, p. 165
ESP_LOGV(TAG, "jde: %f", jde);
ESP_LOGV(TAG, "T: %f", t);
ESP_LOGV(TAG, "L_0: %f", mean_longitude());
ESP_LOGV(TAG, "M: %f", mean_anomaly());
ESP_LOGV(TAG, "e: %f", eccentricity());
ESP_LOGV(TAG, "C: %f", equation_of_center());
ESP_LOGV(TAG, "Odot: %f", true_longitude());
ESP_LOGV(TAG, "Omega: %f", omega());
ESP_LOGV(TAG, "lambda: %f", apparent_longitude());
ESP_LOGV(TAG, "epsilon_0: %f", mean_obliquity());
ESP_LOGV(TAG, "epsilon: %f", true_obliquity());
ESP_LOGV(TAG, "v: %f", true_anomaly());
auto eq = equatorial_coordinate();
ESP_LOGV(TAG, "right_ascension: %f", eq.right_ascension);
ESP_LOGV(TAG, "declination: %f", eq.declination);
ESP_LOGV(TAG,
"jde: %f\n"
"T: %f\n"
"L_0: %f\n"
"M: %f\n"
"e: %f\n"
"C: %f\n"
"Odot: %f\n"
"Omega: %f\n"
"lambda: %f\n"
"epsilon_0: %f\n"
"epsilon: %f\n"
"v: %f\n"
"right_ascension: %f\n"
"declination: %f",
jde, t, mean_longitude(), mean_anomaly(), eccentricity(), equation_of_center(), true_longitude(), omega(),
apparent_longitude(), mean_obliquity(), true_obliquity(), true_anomaly(), eq.right_ascension,
eq.declination);
}
};

View File

@@ -22,8 +22,10 @@ void SX1509FloatOutputChannel::setup() {
}
void SX1509FloatOutputChannel::dump_config() {
ESP_LOGCONFIG(TAG, "SX1509 PWM:");
ESP_LOGCONFIG(TAG, " sx1509 pin: %d", this->pin_);
ESP_LOGCONFIG(TAG,
"SX1509 PWM:\n"
" sx1509 pin: %d",
this->pin_);
LOG_FLOAT_OUTPUT(this);
}

View File

@@ -21,12 +21,14 @@ void TLC5947::setup() {
this->pwm_amounts_.resize(this->num_chips_ * N_CHANNELS_PER_CHIP, 0);
}
void TLC5947::dump_config() {
ESP_LOGCONFIG(TAG, "TLC5947:");
ESP_LOGCONFIG(TAG,
"TLC5947:\n"
" Number of chips: %u",
this->num_chips_);
LOG_PIN(" Data Pin: ", this->data_pin_);
LOG_PIN(" Clock Pin: ", this->clock_pin_);
LOG_PIN(" LAT Pin: ", this->lat_pin_);
LOG_PIN(" OE Pin: ", this->outenable_pin_);
ESP_LOGCONFIG(TAG, " Number of chips: %u", this->num_chips_);
}
void TLC5947::loop() {

View File

@@ -73,12 +73,15 @@ void TMP1075Sensor::set_fault_count(const int faults) {
}
void TMP1075Sensor::log_config_() {
ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot);
ESP_LOGV(TAG, " rate : %d", config_.fields.rate);
ESP_LOGV(TAG, " faults : %d", config_.fields.faults);
ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity);
ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode);
ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown);
ESP_LOGV(TAG,
" oneshot : %d\n"
" rate : %d\n"
" faults : %d\n"
" polarity : %d\n"
" alert_mode: %d\n"
" shutdown : %d",
config_.fields.oneshot, config_.fields.rate, config_.fields.faults, config_.fields.polarity,
config_.fields.alert_mode, config_.fields.shutdown);
}
void TMP1075Sensor::write_config() {

View File

@@ -14,8 +14,10 @@ void TuyaBinarySensor::setup() {
}
void TuyaBinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "Tuya Binary Sensor:");
ESP_LOGCONFIG(TAG, " Binary Sensor has datapoint ID %u", this->sensor_id_);
ESP_LOGCONFIG(TAG,
"Tuya Binary Sensor:\n"
" Binary Sensor has datapoint ID %u",
this->sensor_id_);
}
} // namespace tuya

View File

@@ -33,8 +33,10 @@ void TuyaTextSensor::setup() {
}
void TuyaTextSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Tuya Text Sensor:");
ESP_LOGCONFIG(TAG, " Text Sensor has datapoint ID %u", this->sensor_id_);
ESP_LOGCONFIG(TAG,
"Tuya Text Sensor:\n"
" Text Sensor has datapoint ID %u",
this->sensor_id_);
}
} // namespace tuya

View File

@@ -102,16 +102,16 @@ void UFireECComponent::write_data_(uint8_t reg, float data) {
}
void UFireECComponent::dump_config() {
ESP_LOGCONFIG(TAG, "uFire-EC");
ESP_LOGCONFIG(TAG,
"uFire-EC:\n"
" Temperature Compensation: %f\n"
" Temperature Coefficient: %f",
this->temperature_compensation_, this->temperature_coefficient_);
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_);
LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_);
LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_);
ESP_LOGCONFIG(TAG,
" Temperature Compensation: %f\n"
" Temperature Coefficient: %f",
this->temperature_compensation_, this->temperature_coefficient_);
}
} // namespace ufire_ec

View File

@@ -34,12 +34,14 @@ void ULN2003::loop() {
this->write_step_(this->current_uln_pos_);
}
void ULN2003::dump_config() {
ESP_LOGCONFIG(TAG, "ULN2003:");
ESP_LOGCONFIG(TAG,
"ULN2003:\n"
" Sleep when done: %s",
YESNO(this->sleep_when_done_));
LOG_PIN(" Pin A: ", this->pin_a_);
LOG_PIN(" Pin B: ", this->pin_b_);
LOG_PIN(" Pin C: ", this->pin_c_);
LOG_PIN(" Pin D: ", this->pin_d_);
ESP_LOGCONFIG(TAG, " Sleep when done: %s", YESNO(this->sleep_when_done_));
const char *step_mode_s;
switch (this->step_mode_) {
case ULN2003_STEP_MODE_FULL_STEP:

View File

@@ -39,37 +39,46 @@ static void print_ep_desc(const usb_ep_desc_t *ep_desc) {
break;
}
ESP_LOGV(TAG, "\t\t*** Endpoint descriptor ***");
ESP_LOGV(TAG, "\t\tbLength %d", ep_desc->bLength);
ESP_LOGV(TAG, "\t\tbDescriptorType %d", ep_desc->bDescriptorType);
ESP_LOGV(TAG, "\t\tbEndpointAddress 0x%x\tEP %d %s", ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc),
USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT");
ESP_LOGV(TAG, "\t\tbmAttributes 0x%x\t%s", ep_desc->bmAttributes, ep_type_str);
ESP_LOGV(TAG, "\t\twMaxPacketSize %d", ep_desc->wMaxPacketSize);
ESP_LOGV(TAG, "\t\tbInterval %d", ep_desc->bInterval);
ESP_LOGV(TAG,
"\t\t*** Endpoint descriptor ***\n"
"\t\tbLength %d\n"
"\t\tbDescriptorType %d\n"
"\t\tbEndpointAddress 0x%x\tEP %d %s\n"
"\t\tbmAttributes 0x%x\t%s\n"
"\t\twMaxPacketSize %d\n"
"\t\tbInterval %d",
ep_desc->bLength, ep_desc->bDescriptorType, ep_desc->bEndpointAddress, USB_EP_DESC_GET_EP_NUM(ep_desc),
USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT", ep_desc->bmAttributes, ep_type_str, ep_desc->wMaxPacketSize,
ep_desc->bInterval);
}
static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc) {
ESP_LOGV(TAG, "\t*** Interface descriptor ***");
ESP_LOGV(TAG, "\tbLength %d", intf_desc->bLength);
ESP_LOGV(TAG, "\tbDescriptorType %d", intf_desc->bDescriptorType);
ESP_LOGV(TAG, "\tbInterfaceNumber %d", intf_desc->bInterfaceNumber);
ESP_LOGV(TAG, "\tbAlternateSetting %d", intf_desc->bAlternateSetting);
ESP_LOGV(TAG, "\tbNumEndpoints %d", intf_desc->bNumEndpoints);
ESP_LOGV(TAG, "\tbInterfaceClass 0x%x", intf_desc->bInterfaceProtocol);
ESP_LOGV(TAG, "\tiInterface %d", intf_desc->iInterface);
ESP_LOGV(TAG,
"\t*** Interface descriptor ***\n"
"\tbLength %d\n"
"\tbDescriptorType %d\n"
"\tbInterfaceNumber %d\n"
"\tbAlternateSetting %d\n"
"\tbNumEndpoints %d\n"
"\tbInterfaceClass 0x%x\n"
"\tiInterface %d",
intf_desc->bLength, intf_desc->bDescriptorType, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting,
intf_desc->bNumEndpoints, intf_desc->bInterfaceProtocol, intf_desc->iInterface);
}
static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
ESP_LOGV(TAG, "*** Configuration descriptor ***");
ESP_LOGV(TAG, "bLength %d", cfg_desc->bLength);
ESP_LOGV(TAG, "bDescriptorType %d", cfg_desc->bDescriptorType);
ESP_LOGV(TAG, "wTotalLength %d", cfg_desc->wTotalLength);
ESP_LOGV(TAG, "bNumInterfaces %d", cfg_desc->bNumInterfaces);
ESP_LOGV(TAG, "bConfigurationValue %d", cfg_desc->bConfigurationValue);
ESP_LOGV(TAG, "iConfiguration %d", cfg_desc->iConfiguration);
ESP_LOGV(TAG, "bmAttributes 0x%x", cfg_desc->bmAttributes);
ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2);
ESP_LOGV(TAG,
"*** Configuration descriptor ***\n"
"bLength %d\n"
"bDescriptorType %d\n"
"wTotalLength %d\n"
"bNumInterfaces %d\n"
"bConfigurationValue %d\n"
"iConfiguration %d\n"
"bmAttributes 0x%x\n"
"bMaxPower %dmA",
cfg_desc->bLength, cfg_desc->bDescriptorType, cfg_desc->wTotalLength, cfg_desc->bNumInterfaces,
cfg_desc->bConfigurationValue, cfg_desc->iConfiguration, cfg_desc->bmAttributes, cfg_desc->bMaxPower * 2);
}
static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
@@ -77,21 +86,27 @@ static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_des
return;
}
ESP_LOGV(TAG, "*** Device descriptor ***");
ESP_LOGV(TAG, "bLength %d", devc_desc->bLength);
ESP_LOGV(TAG, "bDescriptorType %d", devc_desc->bDescriptorType);
ESP_LOGV(TAG, "bcdUSB %d.%d0", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF));
ESP_LOGV(TAG, "bDeviceClass 0x%x", devc_desc->bDeviceClass);
ESP_LOGV(TAG, "bDeviceSubClass 0x%x", devc_desc->bDeviceSubClass);
ESP_LOGV(TAG, "bDeviceProtocol 0x%x", devc_desc->bDeviceProtocol);
ESP_LOGV(TAG, "bMaxPacketSize0 %d", devc_desc->bMaxPacketSize0);
ESP_LOGV(TAG, "idVendor 0x%x", devc_desc->idVendor);
ESP_LOGV(TAG, "idProduct 0x%x", devc_desc->idProduct);
ESP_LOGV(TAG, "bcdDevice %d.%d0", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF));
ESP_LOGV(TAG, "iManufacturer %d", devc_desc->iManufacturer);
ESP_LOGV(TAG, "iProduct %d", devc_desc->iProduct);
ESP_LOGV(TAG, "iSerialNumber %d", devc_desc->iSerialNumber);
ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations);
ESP_LOGV(TAG,
"*** Device descriptor ***\n"
"bLength %d\n"
"bDescriptorType %d\n"
"bcdUSB %d.%d0\n"
"bDeviceClass 0x%x\n"
"bDeviceSubClass 0x%x\n"
"bDeviceProtocol 0x%x\n"
"bMaxPacketSize0 %d\n"
"idVendor 0x%x\n"
"idProduct 0x%x\n"
"bcdDevice %d.%d0\n"
"iManufacturer %d\n"
"iProduct %d\n"
"iSerialNumber %d\n"
"bNumConfigurations %d",
devc_desc->bLength, devc_desc->bDescriptorType, ((devc_desc->bcdUSB >> 8) & 0xF),
((devc_desc->bcdUSB >> 4) & 0xF), devc_desc->bDeviceClass, devc_desc->bDeviceSubClass,
devc_desc->bDeviceProtocol, devc_desc->bMaxPacketSize0, devc_desc->idVendor, devc_desc->idProduct,
((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF), devc_desc->iManufacturer,
devc_desc->iProduct, devc_desc->iSerialNumber, devc_desc->bNumConfigurations);
}
static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,

View File

@@ -58,8 +58,10 @@ std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev
ESP_LOGE(TAG, "get_active_config_descriptor failed");
return {};
}
ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u", device_desc->bDeviceClass, device_desc->bDeviceSubClass);
ESP_LOGD(TAG, "bNumInterfaces: %u", config_desc->bNumInterfaces);
ESP_LOGD(TAG,
"bDeviceClass: %u, bDeviceSubClass: %u\n"
"bNumInterfaces: %u",
device_desc->bDeviceClass, device_desc->bDeviceSubClass, config_desc->bNumInterfaces);
if (device_desc->bDeviceClass != 0) {
ESP_LOGE(TAG, "bDeviceClass != 0");
return {};

View File

@@ -27,8 +27,10 @@ void VL53L0XSensor::dump_config() {
if (this->enable_pin_ != nullptr) {
LOG_PIN(" Enable Pin: ", this->enable_pin_);
}
ESP_LOGCONFIG(TAG, " Timeout: %u%s", this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)");
ESP_LOGCONFIG(TAG, " Timing Budget %uus ", this->measurement_timing_budget_us_);
ESP_LOGCONFIG(TAG,
" Timeout: %u%s\n"
" Timing Budget %uus ",
this->timeout_us_, this->timeout_us_ > 0 ? "us" : " (no timeout)", this->measurement_timing_budget_us_);
}
void VL53L0XSensor::setup() {

View File

@@ -430,9 +430,12 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr
}
if (this->api_client_ != nullptr) {
ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant");
ESP_LOGE(TAG, "Current client: %s (%s)", this->api_client_->get_name(), this->api_client_->get_peername());
ESP_LOGE(TAG, "New client: %s (%s)", client->get_name(), client->get_peername());
ESP_LOGE(TAG,
"Multiple API Clients attempting to connect to Voice Assistant\n"
"Current client: %s (%s)\n"
"New client: %s (%s)",
this->api_client_->get_name(), this->api_client_->get_peername(), client->get_name(),
client->get_peername());
return;
}
@@ -864,9 +867,11 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse
.is_active = msg.is_active,
};
this->timers_[timer.id] = timer;
ESP_LOGD(TAG, "Timer Event");
ESP_LOGD(TAG, " Type: %" PRId32, msg.event_type);
ESP_LOGD(TAG, " %s", timer.to_string().c_str());
ESP_LOGD(TAG,
"Timer Event\n"
" Type: %" PRId32 "\n"
" %s",
msg.event_type, timer.to_string().c_str());
switch (msg.event_type) {
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:

View File

@@ -1825,8 +1825,10 @@ void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size
void WaveshareEPaper2P9InV2R2::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 2.9inV2R2");
ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_);
ESP_LOGCONFIG(TAG,
" Model: 2.9inV2R2\n"
" Full Update Every: %" PRIu32,
this->full_update_every_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
@@ -2528,8 +2530,10 @@ int GDEY042T81::get_height_internal() { return 300; }
uint32_t GDEY042T81::idle_timeout_() { return 5000; }
void GDEY042T81::dump_config() {
LOG_DISPLAY("", "GoodDisplay E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 4.2in B/W GDEY042T81");
ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_);
ESP_LOGCONFIG(TAG,
" Model: 4.2in B/W GDEY042T81\n"
" Full Update Every: %" PRIu32,
this->full_update_every_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
@@ -3159,8 +3163,10 @@ int GDEY0583T81::get_height_internal() { return 480; }
uint32_t GDEY0583T81::idle_timeout_() { return 5000; }
void GDEY0583T81::dump_config() {
LOG_DISPLAY("", "GoodDisplay E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 5.83in B/W GDEY0583T81");
ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_);
ESP_LOGCONFIG(TAG,
" Model: 5.83in B/W GDEY0583T81\n"
" Full Update Every: %" PRIu32,
this->full_update_every_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
@@ -4340,8 +4346,10 @@ int WaveshareEPaper7P5InV2P::get_height_internal() { return 480; }
uint32_t WaveshareEPaper7P5InV2P::idle_timeout_() { return 10000; }
void WaveshareEPaper7P5InV2P::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 7.50inv2p");
ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_);
ESP_LOGCONFIG(TAG,
" Model: 7.50inv2p\n"
" Full Update Every: %" PRIu32,
this->full_update_every_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);

View File

@@ -245,6 +245,10 @@ enum WifiMinAuthMode : uint8_t {
struct IDFWiFiEvent;
#endif
#ifdef USE_LIBRETINY
struct LTWiFiEvent;
#endif
/** Listener interface for WiFi IP state changes.
*
* Components can implement this interface to receive IP address updates
@@ -583,6 +587,7 @@ class WiFiComponent : public Component {
#ifdef USE_LIBRETINY
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info);
void wifi_process_event_(LTWiFiEvent *event);
void wifi_scan_done_callback_();
#endif

View File

@@ -3,12 +3,16 @@
#ifdef USE_WIFI
#ifdef USE_LIBRETINY
#include <cinttypes>
#include <utility>
#include <algorithm>
#include "lwip/ip_addr.h"
#include "lwip/err.h"
#include "lwip/dns.h"
#include <FreeRTOS.h>
#include <queue.h>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -19,7 +23,68 @@ namespace esphome::wifi {
static const char *const TAG = "wifi_lt";
static bool s_sta_connecting = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Thread-safe event handling for LibreTiny WiFi
//
// LibreTiny's WiFi.onEvent() callback runs in the WiFi driver's thread context,
// not the main ESPHome loop. Without synchronization, modifying shared state
// (like connection status flags) from the callback causes race conditions:
// - The main loop may never see state changes (values cached in registers)
// - State changes may be visible in inconsistent order
// - LibreTiny targets (BK7231, RTL8720) lack atomic instructions (no LDREX/STREX)
//
// Solution: Queue events in the callback and process them in the main loop.
// This is the same approach used by ESP32 IDF's wifi_process_event_().
// All state modifications happen in the main loop context, eliminating races.
static constexpr size_t EVENT_QUEUE_SIZE = 16; // Max pending WiFi events before overflow
static QueueHandle_t s_event_queue = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static volatile uint32_t s_event_queue_overflow_count =
0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Event structure for queued WiFi events - contains a copy of event data
// to avoid lifetime issues with the original event data from the callback
struct LTWiFiEvent {
arduino_event_id_t event_id;
union {
struct {
uint8_t ssid[33];
uint8_t ssid_len;
uint8_t bssid[6];
uint8_t channel;
uint8_t authmode;
} sta_connected;
struct {
uint8_t ssid[33];
uint8_t ssid_len;
uint8_t bssid[6];
uint8_t reason;
} sta_disconnected;
struct {
uint8_t old_mode;
uint8_t new_mode;
} sta_authmode_change;
struct {
uint32_t status;
uint8_t number;
uint8_t scan_id;
} scan_done;
struct {
uint8_t mac[6];
int rssi;
} ap_probe_req;
} data;
};
// Connection state machine - only modified from main loop after queue processing
enum class LTWiFiSTAState : uint8_t {
IDLE, // Not connecting
CONNECTING, // Connection in progress
CONNECTED, // Successfully connected with IP
ERROR_NOT_FOUND, // AP not found (probe failed)
ERROR_FAILED, // Connection failed (auth, timeout, etc.)
};
static LTWiFiSTAState s_sta_state = LTWiFiSTAState::IDLE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
uint8_t current_mode = WiFi.getMode();
@@ -136,7 +201,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
this->wifi_apply_hostname_();
s_sta_connecting = true;
// Reset state machine before connecting
s_sta_state = LTWiFiSTAState::CONNECTING;
WiFiStatus status = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().empty() ? NULL : ap.get_password().c_str(),
ap.get_channel(), // 0 = auto
@@ -271,16 +337,101 @@ const char *get_disconnect_reason_str(uint8_t reason) {
using esphome_wifi_event_id_t = arduino_event_id_t;
using esphome_wifi_event_info_t = arduino_event_info_t;
// Event callback - runs in WiFi driver thread context
// Only queues events for processing in main loop, no logging or state changes here
void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_wifi_event_info_t info) {
if (s_event_queue == nullptr) {
return;
}
// Allocate on heap and fill directly to avoid extra memcpy
auto *to_send = new LTWiFiEvent{}; // NOLINT(cppcoreguidelines-owning-memory)
to_send->event_id = event;
// Copy event-specific data
switch (event) {
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
auto &it = info.wifi_sta_connected;
to_send->data.sta_connected.ssid_len = it.ssid_len;
memcpy(to_send->data.sta_connected.ssid, it.ssid,
std::min(static_cast<size_t>(it.ssid_len), sizeof(to_send->data.sta_connected.ssid) - 1));
memcpy(to_send->data.sta_connected.bssid, it.bssid, 6);
to_send->data.sta_connected.channel = it.channel;
to_send->data.sta_connected.authmode = it.authmode;
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
auto &it = info.wifi_sta_disconnected;
to_send->data.sta_disconnected.ssid_len = it.ssid_len;
memcpy(to_send->data.sta_disconnected.ssid, it.ssid,
std::min(static_cast<size_t>(it.ssid_len), sizeof(to_send->data.sta_disconnected.ssid) - 1));
memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6);
to_send->data.sta_disconnected.reason = it.reason;
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
auto &it = info.wifi_sta_authmode_change;
to_send->data.sta_authmode_change.old_mode = it.old_mode;
to_send->data.sta_authmode_change.new_mode = it.new_mode;
break;
}
case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
auto &it = info.wifi_scan_done;
to_send->data.scan_done.status = it.status;
to_send->data.scan_done.number = it.number;
to_send->data.scan_done.scan_id = it.scan_id;
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
auto &it = info.wifi_ap_probereqrecved;
memcpy(to_send->data.ap_probe_req.mac, it.mac, 6);
to_send->data.ap_probe_req.rssi = it.rssi;
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
auto &it = info.wifi_sta_connected;
memcpy(to_send->data.sta_connected.bssid, it.bssid, 6);
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto &it = info.wifi_sta_disconnected;
memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6);
break;
}
case ESPHOME_EVENT_ID_WIFI_READY:
case ESPHOME_EVENT_ID_WIFI_STA_START:
case ESPHOME_EVENT_ID_WIFI_STA_STOP:
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP:
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6:
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP:
case ESPHOME_EVENT_ID_WIFI_AP_START:
case ESPHOME_EVENT_ID_WIFI_AP_STOP:
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED:
// No additional data needed
break;
default:
// Unknown event, don't queue
delete to_send; // NOLINT(cppcoreguidelines-owning-memory)
return;
}
// Queue event (don't block if queue is full)
if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) {
delete to_send; // NOLINT(cppcoreguidelines-owning-memory)
s_event_queue_overflow_count++;
}
}
// Process a single event from the queue - runs in main loop context
void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
switch (event->event_id) {
case ESPHOME_EVENT_ID_WIFI_READY: {
ESP_LOGV(TAG, "Ready");
break;
}
case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
auto it = info.wifi_scan_done;
ESP_LOGV(TAG, "Scan done: status=%u number=%u scan_id=%u", it.status, it.number, it.scan_id);
auto &it = event->data.scan_done;
ESP_LOGV(TAG, "Scan done: status=%" PRIu32 " number=%u scan_id=%u", it.status, it.number, it.scan_id);
this->wifi_scan_done_callback_();
break;
}
@@ -291,14 +442,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
ESP_LOGV(TAG, "STA stop");
s_sta_connecting = false;
s_sta_state = LTWiFiSTAState::IDLE;
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
auto it = info.wifi_sta_connected;
auto &it = event->data.sta_connected;
char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.bssid, bssid_buf);
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len,
(const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel,
get_auth_mode_str(it.authmode));
(const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode));
// Note: We don't set CONNECTED state here yet - wait for GOT_IP
// This matches ESP32 IDF behavior where s_sta_connected is set but
// wifi_sta_connect_status_() also checks got_ipv4_address_
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
@@ -306,6 +461,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
s_sta_state = LTWiFiSTAState::CONNECTED;
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
}
@@ -315,19 +471,18 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &it = event->data.sta_disconnected;
// LibreTiny can send spurious disconnect events with empty ssid/bssid during connection.
// These are typically "Association Leave" events that don't indicate actual failures:
// [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave'
// [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave'
// [V][wifi_lt]: Connected ssid='WIFI' bssid=... channel=3, authmode=WPA2 PSK
// Without this check, the spurious events set s_sta_connecting=false, causing
// wifi_sta_connect_status_() to return IDLE. The main loop then sees
// "Unknown connection status 0" (wifi_component.cpp check_connecting_finished)
// and calls retry_connect(), aborting a connection that may succeed moments later.
// Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout.
if (it.ssid_len == 0 && s_sta_connecting) {
// Without this check, the spurious events would transition state to ERROR_FAILED,
// causing wifi_sta_connect_status_() to return an error. The main loop would then
// call retry_connect(), aborting a connection that may succeed moments later.
// Only ignore benign reasons - real failures like NO_AP_FOUND should still be processed.
if (it.ssid_len == 0 && s_sta_state == LTWiFiSTAState::CONNECTING && it.reason != WIFI_REASON_NO_AP_FOUND) {
ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)",
get_disconnect_reason_str(it.reason));
break;
@@ -336,11 +491,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_state = LTWiFiSTAState::ERROR_NOT_FOUND;
} else {
char bssid_s[18];
char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
s_sta_state = LTWiFiSTAState::ERROR_FAILED;
}
uint8_t reason = it.reason;
@@ -351,7 +508,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
this->error_from_callback_ = true;
}
s_sta_connecting = false;
#ifdef USE_WIFI_LISTENERS
static constexpr uint8_t EMPTY_BSSID[6] = {};
for (auto *listener : this->connect_state_listeners_) {
@@ -361,24 +517,22 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
auto it = info.wifi_sta_authmode_change;
auto &it = event->data.sta_authmode_change;
ESP_LOGV(TAG, "Authmode Change old=%s new=%s", get_auth_mode_str(it.old_mode), get_auth_mode_str(it.new_mode));
// Mitigate CVE-2020-12638
// https://lbsfilm.at/blog/wpa2-authenticationmode-downgrade-in-espressif-microprocessors
if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) {
ESP_LOGW(TAG, "Potential Authmode downgrade detected, disconnecting");
// we can't call retry_connect() from this context, so disconnect immediately
// and notify main thread with error_from_callback_
WiFi.disconnect();
this->error_from_callback_ = true;
s_sta_state = LTWiFiSTAState::ERROR_FAILED;
}
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: {
// auto it = info.got_ip.ip_info;
ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(),
format_ip4_addr(WiFi.gatewayIP()).c_str());
s_sta_connecting = false;
s_sta_state = LTWiFiSTAState::CONNECTED;
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
@@ -387,7 +541,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
// auto it = info.got_ip.ip_info;
ESP_LOGV(TAG, "Got IPv6");
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->ip_state_listeners_) {
@@ -398,6 +551,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
ESP_LOGV(TAG, "Lost IP");
// Don't change state to IDLE - let the disconnect event handle that
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_START: {
@@ -409,15 +563,21 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
auto it = info.wifi_sta_connected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
auto &it = event->data.sta_connected;
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.bssid, mac_buf);
ESP_LOGV(TAG, "AP client connected MAC=%s", mac_buf);
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
auto &it = event->data.sta_disconnected;
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.bssid, mac_buf);
ESP_LOGV(TAG, "AP client disconnected MAC=%s", mac_buf);
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
@@ -425,8 +585,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
auto it = info.wifi_ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
auto &it = event->data.ap_probe_req;
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
format_mac_addr_upper(it.mac, mac_buf);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi);
#endif
break;
}
default:
@@ -434,23 +598,35 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
}
void WiFiComponent::wifi_pre_setup_() {
// Create event queue for thread-safe event handling
// Events are pushed from WiFi callback thread and processed in main loop
s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE, sizeof(LTWiFiEvent *));
if (s_event_queue == nullptr) {
ESP_LOGE(TAG, "Failed to create event queue");
return;
}
auto f = std::bind(&WiFiComponent::wifi_event_callback_, this, std::placeholders::_1, std::placeholders::_2);
WiFi.onEvent(f);
// Make sure WiFi is in clean state before anything starts
this->wifi_mode_(false, false);
}
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
auto status = WiFi.status();
if (status == WL_CONNECTED) {
return WiFiSTAConnectStatus::CONNECTED;
} else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) {
return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED;
} else if (status == WL_NO_SSID_AVAIL) {
return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND;
} else if (s_sta_connecting) {
return WiFiSTAConnectStatus::CONNECTING;
// Use state machine instead of querying WiFi.status() directly
// State is updated in main loop from queued events, ensuring thread safety
switch (s_sta_state) {
case LTWiFiSTAState::CONNECTED:
return WiFiSTAConnectStatus::CONNECTED;
case LTWiFiSTAState::ERROR_NOT_FOUND:
return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND;
case LTWiFiSTAState::ERROR_FAILED:
return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED;
case LTWiFiSTAState::CONNECTING:
return WiFiSTAConnectStatus::CONNECTING;
case LTWiFiSTAState::IDLE:
default:
return WiFiSTAConnectStatus::IDLE;
}
return WiFiSTAConnectStatus::IDLE;
}
bool WiFiComponent::wifi_scan_start_(bool passive) {
// enable STA
@@ -534,9 +710,9 @@ network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()};
#endif // USE_WIFI_AP
bool WiFiComponent::wifi_disconnect_() {
// Clear connecting flag first so disconnect events aren't ignored
// Reset state first so disconnect events aren't ignored
// and wifi_sta_connect_status_() returns IDLE instead of CONNECTING
s_sta_connecting = false;
s_sta_state = LTWiFiSTAState::IDLE;
return WiFi.disconnect();
}
@@ -563,7 +739,29 @@ int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
void WiFiComponent::wifi_loop_() {}
void WiFiComponent::wifi_loop_() {
// Process all pending events from the queue
if (s_event_queue == nullptr) {
return;
}
// Check for dropped events due to queue overflow
if (s_event_queue_overflow_count > 0) {
ESP_LOGW(TAG, "Event queue overflow, %" PRIu32 " events dropped", s_event_queue_overflow_count);
s_event_queue_overflow_count = 0;
}
while (true) {
LTWiFiEvent *event;
if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) {
// No more events
break;
}
wifi_process_event_(event);
delete event; // NOLINT(cppcoreguidelines-owning-memory)
}
}
} // namespace esphome::wifi
#endif // USE_LIBRETINY

View File

@@ -131,15 +131,21 @@ void Wireguard::update() {
}
void Wireguard::dump_config() {
ESP_LOGCONFIG(TAG, "WireGuard:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_.c_str());
ESP_LOGCONFIG(TAG, " Netmask: %s", this->netmask_.c_str());
ESP_LOGCONFIG(TAG, " Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str());
ESP_LOGCONFIG(TAG, " Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str());
ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_);
ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str());
ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"),
(!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
// clang-format off
ESP_LOGCONFIG(
TAG,
"WireGuard:\n"
" Address: %s\n"
" Netmask: %s\n"
" Private Key: " LOG_SECRET("%s") "\n"
" Peer Endpoint: " LOG_SECRET("%s") "\n"
" Peer Port: " LOG_SECRET("%d") "\n"
" Peer Public Key: " LOG_SECRET("%s") "\n"
" Peer Pre-shared Key: " LOG_SECRET("%s"),
this->address_.c_str(), this->netmask_.c_str(), mask_key(this->private_key_).c_str(),
this->peer_endpoint_.c_str(), this->peer_port_, this->peer_public_key_.c_str(),
(!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
// clang-format on
ESP_LOGCONFIG(TAG, " Peer Allowed IPs:");
for (auto &allowed_ip : this->allowed_ips_) {
ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str());

View File

@@ -68,12 +68,15 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() {
reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]),
RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1);
ESP_LOGV(TAG, "Tag id: %012lld", reading.id);
ESP_LOGV(TAG, "Country: %03d", reading.country);
ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false");
ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false");
ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0);
ESP_LOGV(TAG, "Reserved1: %" PRId32, reading.reserved1);
ESP_LOGV(TAG,
"Tag id: %012lld\n"
"Country: %03d\n"
"isData: %s\n"
"isAnimal: %s\n"
"Reserved0: %d\n"
"Reserved1: %" PRId32,
reading.id, reading.country, reading.isData ? "true" : "false", reading.isAnimal ? "true" : "false",
reading.reserved0, reading.reserved1);
char buf[20];
sprintf(buf, "%03d%012lld", reading.country, reading.id);

View File

@@ -62,14 +62,14 @@ void X9cOutput::write_state(float state) {
}
void X9cOutput::dump_config() {
ESP_LOGCONFIG(TAG, "X9C Potentiometer Output:");
LOG_PIN(" Chip Select Pin: ", this->cs_pin_);
LOG_PIN(" Increment Pin: ", this->inc_pin_);
LOG_PIN(" Up/Down Pin: ", this->ud_pin_);
ESP_LOGCONFIG(TAG,
"X9C Potentiometer Output:\n"
" Initial Value: %f\n"
" Step Delay: %d",
this->initial_value_, this->step_delay_);
LOG_PIN(" Chip Select Pin: ", this->cs_pin_);
LOG_PIN(" Increment Pin: ", this->inc_pin_);
LOG_PIN(" Up/Down Pin: ", this->ud_pin_);
LOG_FLOAT_OUTPUT(this);
}

View File

@@ -72,8 +72,10 @@ void XGZP68XXComponent::update() {
temperature_raw = encode_uint16(data[3], data[4]);
// Convert the pressure data to hPa
ESP_LOGV(TAG, "Got raw pressure=%" PRIu32 ", raw temperature=%u", pressure_raw, temperature_raw);
ESP_LOGV(TAG, "K value is %u", this->k_value_);
ESP_LOGV(TAG,
"Got raw pressure=%" PRIu32 ", raw temperature=%u\n"
"K value is %u",
pressure_raw, temperature_raw, this->k_value_);
// Sign extend the pressure
float pressure_in_pa = (float) (((int32_t) pressure_raw << 8) >> 8);

View File

@@ -13,8 +13,10 @@ static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16;
void XiaomiXMWSDJ04MMC::dump_config() {
char bindkey_hex[format_hex_pretty_size(XMWSDJ04MMC_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG, "Xiaomi XMWSDJ04MMC");
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.'));
ESP_LOGCONFIG(TAG,
"Xiaomi XMWSDJ04MMC\n"
" Bindkey: %s",
format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);

View File

@@ -59,10 +59,8 @@ void XPT2046Component::update_touches() {
}
void XPT2046Component::dump_config() {
ESP_LOGCONFIG(TAG, "XPT2046:");
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
ESP_LOGCONFIG(TAG,
"XPT2046:\n"
" X min: %d\n"
" X max: %d\n"
" Y min: %d\n"
@@ -73,7 +71,7 @@ void XPT2046Component::dump_config() {
" threshold: %d",
this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_, YESNO(this->swap_x_y_),
YESNO(this->invert_x_), YESNO(this->invert_y_), this->threshold_);
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -721,6 +721,25 @@ class EsphomeCore:
def config_filename(self) -> str:
return self.config_path.name
def has_at_least_one_component(self, *components: str) -> bool:
"""
Are any of the given components configured?
:param components: component names
:return: true if so
"""
if self.config is None:
raise ValueError("Config has not been loaded yet")
return any(component in self.config for component in components)
@property
def has_networking(self) -> bool:
"""
Is a network component configured?
:return: true if so
"""
return self.has_at_least_one_component("wifi", "ethernet", "openthread")
def relative_config_path(self, *path: str | Path) -> Path:
path_ = Path(*path).expanduser()
return self.config_dir / path_

View File

@@ -4,6 +4,7 @@ from esphome.core import CORE
def test_require_wake_loop_threadsafe__first_call() -> None:
"""Test that first call sets up define and consumes socket."""
CORE.config = {"wifi": True}
socket.require_wake_loop_threadsafe()
# Verify CORE.data was updated
@@ -17,6 +18,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None:
"""Test that subsequent calls are idempotent."""
# Set up initial state as if already called
CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
CORE.config = {"ethernet": True}
# Call again - should not raise or fail
socket.require_wake_loop_threadsafe()
@@ -31,6 +33,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None:
def test_require_wake_loop_threadsafe__multiple_calls() -> None:
"""Test that multiple calls only set up once."""
# Call three times
CORE.config = {"openthread": True}
socket.require_wake_loop_threadsafe()
socket.require_wake_loop_threadsafe()
socket.require_wake_loop_threadsafe()
@@ -40,3 +43,35 @@ def test_require_wake_loop_threadsafe__multiple_calls() -> None:
# Verify the define was added (only once, but we can just check it exists)
assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines)
def test_require_wake_loop_threadsafe__no_networking() -> None:
"""Test that wake loop is NOT configured when no networking is configured."""
# Set up config without any networking components
CORE.config = {"esphome": {"name": "test"}, "logger": {}}
# Call require_wake_loop_threadsafe
socket.require_wake_loop_threadsafe()
# Verify CORE.data flag was NOT set (since has_networking returns False)
assert socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED not in CORE.data
# Verify the define was NOT added
assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines)
def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -> None:
"""Test that no socket is consumed when no networking is configured."""
# Set up config without any networking components
CORE.config = {"logger": {}}
# Track initial socket consumer state
initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {})
# Call require_wake_loop_threadsafe
socket.require_wake_loop_threadsafe()
# Verify no socket was consumed
consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {})
assert "socket.wake_loop_threadsafe" not in consumers
assert consumers == initial_consumers

View File

@@ -718,3 +718,65 @@ class TestEsphomeCore:
# Even though "web_server" is in loaded_integrations due to the platform,
# web_port must return None because the full web_server component is not configured
assert target.web_port is None
def test_has_at_least_one_component__none_configured(self, target):
"""Test has_at_least_one_component returns False when none of the components are configured."""
target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}}
assert target.has_at_least_one_component("wifi", "ethernet") is False
def test_has_at_least_one_component__one_configured(self, target):
"""Test has_at_least_one_component returns True when one component is configured."""
target.config = {const.CONF_WIFI: {}, "logger": {}}
assert target.has_at_least_one_component("wifi", "ethernet") is True
def test_has_at_least_one_component__multiple_configured(self, target):
"""Test has_at_least_one_component returns True when multiple components are configured."""
target.config = {
const.CONF_WIFI: {},
const.CONF_ETHERNET: {},
"logger": {},
}
assert (
target.has_at_least_one_component("wifi", "ethernet", "bluetooth") is True
)
def test_has_at_least_one_component__single_component(self, target):
"""Test has_at_least_one_component works with a single component."""
target.config = {const.CONF_MQTT: {}}
assert target.has_at_least_one_component("mqtt") is True
assert target.has_at_least_one_component("wifi") is False
def test_has_at_least_one_component__config_not_loaded(self, target):
"""Test has_at_least_one_component raises ValueError when config is not loaded."""
target.config = None
with pytest.raises(ValueError, match="Config has not been loaded yet"):
target.has_at_least_one_component("wifi")
def test_has_networking__with_wifi(self, target):
"""Test has_networking returns True when wifi is configured."""
target.config = {const.CONF_WIFI: {}}
assert target.has_networking is True
def test_has_networking__with_ethernet(self, target):
"""Test has_networking returns True when ethernet is configured."""
target.config = {const.CONF_ETHERNET: {}}
assert target.has_networking is True
def test_has_networking__with_openthread(self, target):
"""Test has_networking returns True when openthread is configured."""
target.config = {const.CONF_OPENTHREAD: {}}
assert target.has_networking is True
def test_has_networking__without_networking(self, target):
"""Test has_networking returns False when no networking component is configured."""
target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}}
assert target.has_networking is False