1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-19 00:05:43 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Claude
0c4ab546de [axp2101] Use walrus operator to avoid double dict lookup 2025-11-18 19:10:07 +00:00
Claude
8e00e895e0 [axp2101] Code review fixes
- Merge update() and update_sensors() methods
- Remove unnecessary variable assignments before returns
- Add #ifdef USE_SENSOR guards around sensor-related code
- Restructure number and switch to be directories for better isolation
- Use proper enum codegen helpers for PowerRail enum
2025-11-18 19:05:21 +00:00
Claude
9dba37962c [axp2101] Add AXP2101 PMIC component
This commit adds support for the AXP2101 Power Management IC (PMIC).

Features:
- Hub component for I2C communication with AXP2101
- Sensor support for monitoring:
  - Battery voltage, level (percentage)
  - VBUS voltage
  - VSYS voltage
  - Die temperature
- Switch support for enabling/disabling power rails:
  - 5 DCDC regulators (DCDC1-5)
  - 11 LDO regulators (ALDO1-4, BLDO1-2, CPUSLDO, DLDO1-2)
- Number support for voltage control of all power rails
- Each rail supports its specific voltage range and step size
- Comprehensive test configurations for ESP32, ESP8266, and RP2040

The component follows ESPHome patterns with a hub-based architecture
allowing sensors, switches, and numbers to reference the main component.
2025-11-18 09:53:20 +00:00
38 changed files with 1203 additions and 243 deletions

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,29 @@
"""AXP2101 Power Management IC component for ESPHome."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
axp2101_ns = cg.esphome_ns.namespace("axp2101")
AXP2101Component = axp2101_ns.class_("AXP2101Component", cg.PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AXP2101Component),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x34))
)
async def to_code(config):
"""Generate code for the AXP2101 component."""
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -0,0 +1,369 @@
/**
* @file axp2101.cpp
* @brief Implementation of AXP2101 Power Management IC Component
*/
#include "axp2101.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace axp2101 {
static const char *const TAG = "axp2101";
// Temperature conversion constant (from datasheet)
static const float TEMP_CONVERSION_FACTOR = 0.1f;
static const float TEMP_OFFSET = -267.15f;
void AXP2101Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AXP2101...");
// Read chip ID to verify communication
uint8_t chip_id;
if (!this->read_byte(AXP2101_IC_TYPE, &chip_id)) {
ESP_LOGE(TAG, "Failed to read chip ID");
this->mark_failed();
return;
}
ESP_LOGD(TAG, "Chip ID: 0x%02X", chip_id);
// Enable ADC channels for monitoring
// Enable battery voltage, VBUS, VSYS, and temperature measurements
uint8_t adc_ctrl = 0xFF; // Enable all ADC channels
if (!this->write_byte(AXP2101_ADC_CHANNEL_CTRL, adc_ctrl)) {
ESP_LOGW(TAG, "Failed to configure ADC channels");
}
ESP_LOGCONFIG(TAG, "AXP2101 setup complete");
}
void AXP2101Component::dump_config() {
ESP_LOGCONFIG(TAG, "AXP2101:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with AXP2101 failed!");
return;
}
#ifdef USE_SENSOR
// Log sensor configuration
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_sensor_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_sensor_);
LOG_SENSOR(" ", "VBUS Voltage", this->vbus_voltage_sensor_);
LOG_SENSOR(" ", "VSYS Voltage", this->vsys_voltage_sensor_);
LOG_SENSOR(" ", "Die Temperature", this->die_temperature_sensor_);
#endif
}
void AXP2101Component::update() {
#ifdef USE_SENSOR
if (this->battery_voltage_sensor_ != nullptr) {
this->battery_voltage_sensor_->publish_state(this->read_battery_voltage() / 1000.0f);
}
if (this->battery_level_sensor_ != nullptr) {
this->battery_level_sensor_->publish_state(this->read_battery_level());
}
if (this->vbus_voltage_sensor_ != nullptr) {
this->vbus_voltage_sensor_->publish_state(this->read_vbus_voltage() / 1000.0f);
}
if (this->vsys_voltage_sensor_ != nullptr) {
this->vsys_voltage_sensor_->publish_state(this->read_vsys_voltage() / 1000.0f);
}
if (this->die_temperature_sensor_ != nullptr) {
this->die_temperature_sensor_->publish_state(this->read_die_temperature());
}
#endif
}
bool AXP2101Component::set_register_bit(uint8_t reg, uint8_t bit) {
uint8_t value;
if (!this->read_byte(reg, &value)) {
return false;
}
value |= (1 << bit);
return this->write_byte(reg, value);
}
bool AXP2101Component::clear_register_bit(uint8_t reg, uint8_t bit) {
uint8_t value;
if (!this->read_byte(reg, &value)) {
return false;
}
value &= ~(1 << bit);
return this->write_byte(reg, value);
}
bool AXP2101Component::get_register_bit(uint8_t reg, uint8_t bit) {
uint8_t value;
if (!this->read_byte(reg, &value)) {
return false;
}
return (value & (1 << bit)) != 0;
}
uint16_t AXP2101Component::read_register_h5l8(uint8_t high_reg, uint8_t low_reg) {
uint8_t high, low;
if (!this->read_byte(high_reg, &high) || !this->read_byte(low_reg, &low)) {
return 0;
}
return ((high & 0x1F) << 8) | low;
}
uint16_t AXP2101Component::read_register_h6l8(uint8_t high_reg, uint8_t low_reg) {
uint8_t high, low;
if (!this->read_byte(high_reg, &high) || !this->read_byte(low_reg, &low)) {
return 0;
}
return ((high & 0x3F) << 8) | low;
}
bool AXP2101Component::get_rail_enable_info(PowerRail rail, uint8_t &reg, uint8_t &bit) {
// DCDC rails are controlled by DC_ONOFF_DVM_CTRL register
if (rail >= DCDC1 && rail <= DCDC5) {
reg = AXP2101_DC_ONOFF_DVM_CTRL;
bit = static_cast<uint8_t>(rail); // DCDC1=bit0, DCDC2=bit1, etc.
return true;
}
// LDO rails are controlled by LDO_ONOFF_CTRL0 and LDO_ONOFF_CTRL1
if (rail >= ALDO1 && rail <= DLDO2) {
if (rail <= BLDO2) {
reg = AXP2101_LDO_ONOFF_CTRL0;
bit = static_cast<uint8_t>(rail - ALDO1); // ALDO1=bit0, ALDO2=bit1, etc.
} else {
reg = AXP2101_LDO_ONOFF_CTRL1;
bit = static_cast<uint8_t>(rail - CPUSLDO); // CPUSLDO=bit0, DLDO1=bit1, DLDO2=bit2
}
return true;
}
return false;
}
bool AXP2101Component::get_rail_voltage_info(PowerRail rail, uint8_t &reg, uint16_t &min_mv, uint16_t &max_mv,
uint16_t &step_mv) {
switch (rail) {
case DCDC1:
reg = AXP2101_DC_VOL0_CTRL;
min_mv = 1500;
max_mv = 3400;
step_mv = 100;
return true;
case DCDC2:
case DCDC3:
case DCDC4:
// These have dual ranges, handle in calculate methods
reg = AXP2101_DC_VOL1_CTRL + (rail - DCDC2);
min_mv = 500;
max_mv = 1540;
step_mv = 10;
return true;
case DCDC5:
reg = AXP2101_DC_VOL4_CTRL;
min_mv = 1400;
max_mv = 3700;
step_mv = 100;
return true;
case ALDO1:
case ALDO2:
case ALDO3:
case ALDO4:
reg = AXP2101_ALDO1_VOL_CTRL + (rail - ALDO1);
min_mv = 500;
max_mv = 3500;
step_mv = 100;
return true;
case BLDO1:
case BLDO2:
reg = AXP2101_BLDO1_VOL_CTRL + (rail - BLDO1);
min_mv = 500;
max_mv = 3500;
step_mv = 100;
return true;
case CPUSLDO:
reg = AXP2101_CPUSLDO_VOL_CTRL;
min_mv = 500;
max_mv = 1400;
step_mv = 50;
return true;
case DLDO1:
case DLDO2:
reg = AXP2101_DLDO1_VOL_CTRL + (rail - DLDO1);
min_mv = 500;
max_mv = 3400;
step_mv = 100;
return true;
default:
return false;
}
}
uint8_t AXP2101Component::calculate_voltage_register_value(PowerRail rail, uint16_t millivolts) {
// Special handling for DCDC2, DCDC3, DCDC4 with dual ranges
if (rail == DCDC2 || rail == DCDC3 || rail == DCDC4) {
if (millivolts >= 500 && millivolts <= 1200) {
// Range 1: 500-1200mV in 10mV steps
return static_cast<uint8_t>((millivolts - 500) / 10);
} else if (millivolts >= 1220 && millivolts <= 1540) {
// Range 2: 1220-1540mV in 20mV steps
return static_cast<uint8_t>(70 + (millivolts - 1220) / 20);
} else if (rail == DCDC3 && millivolts >= 1600 && millivolts <= 3400) {
// DCDC3 has an extended range
return static_cast<uint8_t>(87 + (millivolts - 1600) / 100);
}
return 0; // Invalid voltage
}
// Special handling for DCDC4 extended range
if (rail == DCDC4 && millivolts >= 1600 && millivolts <= 1840) {
return static_cast<uint8_t>(87 + (millivolts - 1600) / 20);
}
// Standard calculation for other rails
uint8_t reg;
uint16_t min_mv, max_mv, step_mv;
if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) {
return 0;
}
if (millivolts < min_mv || millivolts > max_mv) {
return 0; // Out of range
}
if ((millivolts - min_mv) % step_mv != 0) {
return 0; // Not aligned to step size
}
return static_cast<uint8_t>((millivolts - min_mv) / step_mv);
}
uint16_t AXP2101Component::calculate_millivolts_from_register(PowerRail rail, uint8_t reg_value) {
// Special handling for DCDC2, DCDC3, DCDC4 with dual ranges
if (rail == DCDC2 || rail == DCDC3 || rail == DCDC4) {
if (reg_value <= 70) {
// Range 1: 500-1200mV in 10mV steps
return 500 + (reg_value * 10);
} else if (reg_value <= 86) {
// Range 2: 1220-1540mV in 20mV steps
return 1220 + ((reg_value - 70) * 20);
} else if (rail == DCDC3 && reg_value <= 105) {
// DCDC3 extended range: 1600-3400mV in 100mV steps
return 1600 + ((reg_value - 87) * 100);
} else if (rail == DCDC4 && reg_value <= 99) {
// DCDC4 extended range: 1600-1840mV in 20mV steps
return 1600 + ((reg_value - 87) * 20);
}
return 0; // Invalid
}
// Standard calculation for other rails
uint8_t reg;
uint16_t min_mv, max_mv, step_mv;
if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) {
return 0;
}
return min_mv + (reg_value * step_mv);
}
bool AXP2101Component::enable_power_rail(PowerRail rail) {
uint8_t reg, bit;
if (!this->get_rail_enable_info(rail, reg, bit)) {
return false;
}
return this->set_register_bit(reg, bit);
}
bool AXP2101Component::disable_power_rail(PowerRail rail) {
uint8_t reg, bit;
if (!this->get_rail_enable_info(rail, reg, bit)) {
return false;
}
return this->clear_register_bit(reg, bit);
}
bool AXP2101Component::is_power_rail_enabled(PowerRail rail) {
uint8_t reg, bit;
if (!this->get_rail_enable_info(rail, reg, bit)) {
return false;
}
return this->get_register_bit(reg, bit);
}
bool AXP2101Component::set_rail_voltage(PowerRail rail, uint16_t millivolts) {
uint8_t reg;
uint16_t min_mv, max_mv, step_mv;
if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) {
ESP_LOGE(TAG, "Invalid power rail");
return false;
}
uint8_t value = this->calculate_voltage_register_value(rail, millivolts);
if (value == 0 && millivolts != 0) {
ESP_LOGE(TAG, "Invalid voltage %u mV for rail", millivolts);
return false;
}
return this->write_byte(reg, value);
}
uint16_t AXP2101Component::get_rail_voltage(PowerRail rail) {
uint8_t reg;
uint16_t min_mv, max_mv, step_mv;
if (!this->get_rail_voltage_info(rail, reg, min_mv, max_mv, step_mv)) {
return 0;
}
uint8_t value;
if (!this->read_byte(reg, &value)) {
return 0;
}
return this->calculate_millivolts_from_register(rail, value & 0x7F);
}
uint16_t AXP2101Component::read_battery_voltage() {
// 1 LSB = 1mV
return this->read_register_h5l8(AXP2101_ADC_DATA_RELUST0, AXP2101_ADC_DATA_RELUST1);
}
uint16_t AXP2101Component::read_vbus_voltage() {
// 1 LSB = 1mV
return this->read_register_h5l8(AXP2101_ADC_DATA_RELUST4, AXP2101_ADC_DATA_RELUST5);
}
uint16_t AXP2101Component::read_vsys_voltage() {
// 1 LSB = 1mV
return this->read_register_h6l8(AXP2101_ADC_DATA_RELUST6, AXP2101_ADC_DATA_RELUST7);
}
float AXP2101Component::read_die_temperature() {
// Temperature conversion: T = raw * 0.1 - 267.15
return (this->read_register_h6l8(AXP2101_ADC_DATA_RELUST8, AXP2101_ADC_DATA_RELUST9) * TEMP_CONVERSION_FACTOR) +
TEMP_OFFSET;
}
uint8_t AXP2101Component::read_battery_level() {
uint8_t level;
if (!this->read_byte(AXP2101_BAT_PERCENT_DATA, &level)) {
return 0;
}
return level > 100 ? 100 : level;
}
} // namespace axp2101
} // namespace esphome

View File

@@ -0,0 +1,175 @@
/**
* @file axp2101.h
* @brief AXP2101 Power Management IC Component
*
* This component provides access to the AXP2101 PMIC which includes:
* - 5 DCDC regulators (DCDC1-5)
* - 11 LDO regulators (ALDO1-4, BLDO1-2, CPUSLDO, DLDO1-2)
* - Battery management and charging
* - ADC monitoring (battery voltage, current, temperature, etc.)
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace axp2101 {
// Register addresses
static const uint8_t AXP2101_STATUS1 = 0x00;
static const uint8_t AXP2101_STATUS2 = 0x01;
static const uint8_t AXP2101_IC_TYPE = 0x03;
static const uint8_t AXP2101_COMMON_CONFIG = 0x10;
static const uint8_t AXP2101_BATFET_CTRL = 0x12;
static const uint8_t AXP2101_MIN_SYS_VOL_CTRL = 0x14;
static const uint8_t AXP2101_INPUT_VOL_LIMIT_CTRL = 0x15;
static const uint8_t AXP2101_INPUT_CUR_LIMIT_CTRL = 0x16;
static const uint8_t AXP2101_PWRON_STATUS = 0x20;
static const uint8_t AXP2101_PWROFF_STATUS = 0x21;
static const uint8_t AXP2101_PWROFF_EN = 0x22;
// ADC control and data registers
static const uint8_t AXP2101_ADC_CHANNEL_CTRL = 0x30;
static const uint8_t AXP2101_ADC_DATA_RELUST0 = 0x34; // Battery voltage high
static const uint8_t AXP2101_ADC_DATA_RELUST1 = 0x35; // Battery voltage low
static const uint8_t AXP2101_ADC_DATA_RELUST2 = 0x36; // TS voltage high
static const uint8_t AXP2101_ADC_DATA_RELUST3 = 0x37; // TS voltage low
static const uint8_t AXP2101_ADC_DATA_RELUST4 = 0x38; // VBUS voltage high
static const uint8_t AXP2101_ADC_DATA_RELUST5 = 0x39; // VBUS voltage low
static const uint8_t AXP2101_ADC_DATA_RELUST6 = 0x3A; // VSYS voltage high
static const uint8_t AXP2101_ADC_DATA_RELUST7 = 0x3B; // VSYS voltage low
static const uint8_t AXP2101_ADC_DATA_RELUST8 = 0x3C; // Die temperature high
static const uint8_t AXP2101_ADC_DATA_RELUST9 = 0x3D; // Die temperature low
// Interrupt registers
static const uint8_t AXP2101_INTEN1 = 0x40;
static const uint8_t AXP2101_INTEN2 = 0x41;
static const uint8_t AXP2101_INTEN3 = 0x42;
static const uint8_t AXP2101_INTSTS1 = 0x48;
static const uint8_t AXP2101_INTSTS2 = 0x49;
static const uint8_t AXP2101_INTSTS3 = 0x4A;
// Charging configuration registers
static const uint8_t AXP2101_CHARGE_GAUGE_WDT_CTRL = 0x18;
static const uint8_t AXP2101_IPRECHG_SET = 0x61;
static const uint8_t AXP2101_ICC_CHG_SET = 0x62;
static const uint8_t AXP2101_ITERM_CHG_SET_CTRL = 0x63;
static const uint8_t AXP2101_CV_CHG_VOL_SET = 0x64;
static const uint8_t AXP2101_CHARGE_TIMEOUT_SET_CTRL = 0x67;
static const uint8_t AXP2101_BAT_DET_CTRL = 0x68;
static const uint8_t AXP2101_CHGLED_SET_CTRL = 0x69;
static const uint8_t AXP2101_BTN_BAT_CHG_VOL_SET = 0x6A;
// DCDC control registers
static const uint8_t AXP2101_DC_ONOFF_DVM_CTRL = 0x80;
static const uint8_t AXP2101_DC_VOL0_CTRL = 0x82; // DCDC1
static const uint8_t AXP2101_DC_VOL1_CTRL = 0x83; // DCDC2
static const uint8_t AXP2101_DC_VOL2_CTRL = 0x84; // DCDC3
static const uint8_t AXP2101_DC_VOL3_CTRL = 0x85; // DCDC4
static const uint8_t AXP2101_DC_VOL4_CTRL = 0x86; // DCDC5
// LDO control registers
static const uint8_t AXP2101_LDO_ONOFF_CTRL0 = 0x90;
static const uint8_t AXP2101_LDO_ONOFF_CTRL1 = 0x91;
static const uint8_t AXP2101_ALDO1_VOL_CTRL = 0x92;
static const uint8_t AXP2101_ALDO2_VOL_CTRL = 0x93;
static const uint8_t AXP2101_ALDO3_VOL_CTRL = 0x94;
static const uint8_t AXP2101_ALDO4_VOL_CTRL = 0x95;
static const uint8_t AXP2101_BLDO1_VOL_CTRL = 0x96;
static const uint8_t AXP2101_BLDO2_VOL_CTRL = 0x97;
static const uint8_t AXP2101_CPUSLDO_VOL_CTRL = 0x98;
static const uint8_t AXP2101_DLDO1_VOL_CTRL = 0x99;
static const uint8_t AXP2101_DLDO2_VOL_CTRL = 0x9A;
// Battery gauge registers
static const uint8_t AXP2101_BAT_PARAMS = 0xA1;
static const uint8_t AXP2101_BAT_GAUGE_CTRL = 0xA2;
static const uint8_t AXP2101_BAT_PERCENT_DATA = 0xA4;
// Power rail identifiers
enum PowerRail {
DCDC1 = 0,
DCDC2,
DCDC3,
DCDC4,
DCDC5,
ALDO1,
ALDO2,
ALDO3,
ALDO4,
BLDO1,
BLDO2,
CPUSLDO,
DLDO1,
DLDO2,
};
/**
* @brief Main AXP2101 component class
*
* This component handles communication with the AXP2101 PMIC via I2C.
* It provides methods for power rail control, voltage adjustment,
* and monitoring of various parameters.
*/
class AXP2101Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
#ifdef USE_SENSOR
// Sensor setters
void set_battery_voltage_sensor(sensor::Sensor *sensor) { this->battery_voltage_sensor_ = sensor; }
void set_battery_current_sensor(sensor::Sensor *sensor) { this->battery_current_sensor_ = sensor; }
void set_battery_level_sensor(sensor::Sensor *sensor) { this->battery_level_sensor_ = sensor; }
void set_vbus_voltage_sensor(sensor::Sensor *sensor) { this->vbus_voltage_sensor_ = sensor; }
void set_vsys_voltage_sensor(sensor::Sensor *sensor) { this->vsys_voltage_sensor_ = sensor; }
void set_die_temperature_sensor(sensor::Sensor *sensor) { this->die_temperature_sensor_ = sensor; }
#endif
// Power rail control methods
bool enable_power_rail(PowerRail rail);
bool disable_power_rail(PowerRail rail);
bool is_power_rail_enabled(PowerRail rail);
// Voltage control methods
bool set_rail_voltage(PowerRail rail, uint16_t millivolts);
uint16_t get_rail_voltage(PowerRail rail);
// ADC reading methods
uint16_t read_battery_voltage();
uint16_t read_vbus_voltage();
uint16_t read_vsys_voltage();
float read_die_temperature();
uint8_t read_battery_level();
protected:
// Helper methods for register access
bool set_register_bit(uint8_t reg, uint8_t bit);
bool clear_register_bit(uint8_t reg, uint8_t bit);
bool get_register_bit(uint8_t reg, uint8_t bit);
uint16_t read_register_h5l8(uint8_t high_reg, uint8_t low_reg);
uint16_t read_register_h6l8(uint8_t high_reg, uint8_t low_reg);
// Power rail helper methods
bool get_rail_enable_info(PowerRail rail, uint8_t &reg, uint8_t &bit);
bool get_rail_voltage_info(PowerRail rail, uint8_t &reg, uint16_t &min_mv, uint16_t &max_mv, uint16_t &step_mv);
uint8_t calculate_voltage_register_value(PowerRail rail, uint16_t millivolts);
uint16_t calculate_millivolts_from_register(PowerRail rail, uint8_t reg_value);
#ifdef USE_SENSOR
// Sensor pointers (optional)
sensor::Sensor *battery_voltage_sensor_{nullptr};
sensor::Sensor *battery_current_sensor_{nullptr};
sensor::Sensor *battery_level_sensor_{nullptr};
sensor::Sensor *vbus_voltage_sensor_{nullptr};
sensor::Sensor *vsys_voltage_sensor_{nullptr};
sensor::Sensor *die_temperature_sensor_{nullptr};
#endif
};
} // namespace axp2101
} // namespace esphome

View File

@@ -0,0 +1,90 @@
"""Number support for AXP2101 voltage control."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import number
from esphome.const import CONF_ID, UNIT_VOLT
from .. import AXP2101Component, axp2101_ns
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["axp2101"]
CONF_AXP2101_ID = "axp2101_id"
CONF_POWER_RAIL = "power_rail"
AXP2101Number = axp2101_ns.class_("AXP2101Number", number.Number, cg.Component)
# Power rail enum matching the PowerRail enum in C++
PowerRail = axp2101_ns.enum("PowerRail")
POWER_RAILS = {
"DCDC1": PowerRail.DCDC1,
"DCDC2": PowerRail.DCDC2,
"DCDC3": PowerRail.DCDC3,
"DCDC4": PowerRail.DCDC4,
"DCDC5": PowerRail.DCDC5,
"ALDO1": PowerRail.ALDO1,
"ALDO2": PowerRail.ALDO2,
"ALDO3": PowerRail.ALDO3,
"ALDO4": PowerRail.ALDO4,
"BLDO1": PowerRail.BLDO1,
"BLDO2": PowerRail.BLDO2,
"CPUSLDO": PowerRail.CPUSLDO,
"DLDO1": PowerRail.DLDO1,
"DLDO2": PowerRail.DLDO2,
}
# Voltage ranges for each power rail (in millivolts)
VOLTAGE_RANGES = {
"DCDC1": {"min": 1500, "max": 3400, "step": 100},
"DCDC2": {"min": 500, "max": 1540, "step": 10},
"DCDC3": {"min": 500, "max": 3400, "step": 10},
"DCDC4": {"min": 500, "max": 1840, "step": 10},
"DCDC5": {"min": 1400, "max": 3700, "step": 100},
"ALDO1": {"min": 500, "max": 3500, "step": 100},
"ALDO2": {"min": 500, "max": 3500, "step": 100},
"ALDO3": {"min": 500, "max": 3500, "step": 100},
"ALDO4": {"min": 500, "max": 3500, "step": 100},
"BLDO1": {"min": 500, "max": 3500, "step": 100},
"BLDO2": {"min": 500, "max": 3500, "step": 100},
"CPUSLDO": {"min": 500, "max": 1400, "step": 50},
"DLDO1": {"min": 500, "max": 3400, "step": 100},
"DLDO2": {"min": 500, "max": 3400, "step": 100},
}
def validate_voltage_range(config):
"""Validate that voltage is within the allowed range for the power rail."""
rail = config[CONF_POWER_RAIL]
ranges = VOLTAGE_RANGES[rail]
# Update the number schema with the correct min/max/step
return config
CONFIG_SCHEMA = cv.All(
number.number_schema(
AXP2101Number,
unit_of_measurement=UNIT_VOLT,
).extend(
{
cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component),
cv.Required(CONF_POWER_RAIL): cv.enum(POWER_RAILS, upper=True),
}
),
validate_voltage_range,
)
async def to_code(config):
"""Generate code for AXP2101 number."""
paren = await cg.get_variable(config[CONF_AXP2101_ID])
var = await number.new_number(config, min_value=0.5, max_value=3.7, step=0.01)
await cg.register_component(var, config)
cg.add(var.set_parent(paren))
cg.add(var.set_power_rail(config[CONF_POWER_RAIL]))
# Set rail-specific voltage range
rail = config[CONF_POWER_RAIL]
ranges = VOLTAGE_RANGES[rail]
cg.add(var.set_voltage_range(ranges["min"], ranges["max"], ranges["step"]))

View File

@@ -0,0 +1,101 @@
/**
* @file axp2101_number.cpp
* @brief Implementation of AXP2101 number component
*/
#include "axp2101_number.h"
#include "esphome/core/log.h"
namespace esphome {
namespace axp2101 {
static const char *const TAG = "axp2101.number";
static const char *power_rail_to_string(PowerRail rail) {
switch (rail) {
case DCDC1:
return "DCDC1";
case DCDC2:
return "DCDC2";
case DCDC3:
return "DCDC3";
case DCDC4:
return "DCDC4";
case DCDC5:
return "DCDC5";
case ALDO1:
return "ALDO1";
case ALDO2:
return "ALDO2";
case ALDO3:
return "ALDO3";
case ALDO4:
return "ALDO4";
case BLDO1:
return "BLDO1";
case BLDO2:
return "BLDO2";
case CPUSLDO:
return "CPUSLDO";
case DLDO1:
return "DLDO1";
case DLDO2:
return "DLDO2";
default:
return "UNKNOWN";
}
}
void AXP2101Number::setup() {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
this->mark_failed();
return;
}
// Read current voltage and publish initial state
uint16_t current_mv = this->parent_->get_rail_voltage(this->rail_);
if (current_mv > 0) {
float current_v = current_mv / 1000.0f;
this->publish_state(current_v);
}
}
void AXP2101Number::dump_config() {
LOG_NUMBER("", "AXP2101 Number", this);
ESP_LOGCONFIG(TAG, " Power Rail: %s", power_rail_to_string(this->rail_));
ESP_LOGCONFIG(TAG, " Voltage Range: %.2f-%.2fV (step: %.3fV)", this->min_mv_ / 1000.0f, this->max_mv_ / 1000.0f,
this->step_mv_ / 1000.0f);
}
void AXP2101Number::control(float value) {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
return;
}
// Convert volts to millivolts
uint16_t millivolts = static_cast<uint16_t>(value * 1000.0f);
// Validate range
if (millivolts < this->min_mv_ || millivolts > this->max_mv_) {
ESP_LOGW(TAG, "Voltage %.3fV out of range for %s (%.2f-%.2fV)", value, power_rail_to_string(this->rail_),
this->min_mv_ / 1000.0f, this->max_mv_ / 1000.0f);
return;
}
// Round to nearest step
uint16_t offset = millivolts - this->min_mv_;
uint16_t steps = (offset + this->step_mv_ / 2) / this->step_mv_;
millivolts = this->min_mv_ + (steps * this->step_mv_);
if (this->parent_->set_rail_voltage(this->rail_, millivolts)) {
float actual_v = millivolts / 1000.0f;
this->publish_state(actual_v);
ESP_LOGD(TAG, "Set %s voltage to %.3fV", power_rail_to_string(this->rail_), actual_v);
} else {
ESP_LOGE(TAG, "Failed to set %s voltage to %.3fV", power_rail_to_string(this->rail_), value);
}
}
} // namespace axp2101
} // namespace esphome

View File

@@ -0,0 +1,44 @@
/**
* @file axp2101_number.h
* @brief Number component for AXP2101 voltage control
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/number/number.h"
#include "../axp2101.h"
namespace esphome {
namespace axp2101 {
/**
* @brief Number component for controlling AXP2101 power rail voltages
*
* This number component allows setting the output voltage of individual
* power rails (DCDCs and LDOs).
*/
class AXP2101Number : public number::Number, public Component {
public:
void set_parent(AXP2101Component *parent) { this->parent_ = parent; }
void set_power_rail(PowerRail rail) { this->rail_ = rail; }
void set_voltage_range(uint16_t min_mv, uint16_t max_mv, uint16_t step_mv) {
this->min_mv_ = min_mv;
this->max_mv_ = max_mv;
this->step_mv_ = step_mv;
}
void setup() override;
void dump_config() override;
protected:
void control(float value) override;
AXP2101Component *parent_;
PowerRail rail_;
uint16_t min_mv_{500};
uint16_t max_mv_{3500};
uint16_t step_mv_{100};
};
} // namespace axp2101
} // namespace esphome

View File

@@ -0,0 +1,86 @@
"""Sensor support for AXP2101."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_BATTERY_LEVEL,
CONF_BATTERY_VOLTAGE,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_VOLT,
)
from . import AXP2101Component, axp2101_ns
DEPENDENCIES = ["axp2101"]
CONF_AXP2101_ID = "axp2101_id"
CONF_VBUS_VOLTAGE = "vbus_voltage"
CONF_VSYS_VOLTAGE = "vsys_voltage"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VBUS_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VSYS_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
async def to_code(config):
"""Generate code for AXP2101 sensors."""
paren = await cg.get_variable(config[CONF_AXP2101_ID])
if conf := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(conf)
cg.add(paren.set_battery_voltage_sensor(sens))
if conf := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(conf)
cg.add(paren.set_battery_level_sensor(sens))
if conf := config.get(CONF_VBUS_VOLTAGE):
sens = await sensor.new_sensor(conf)
cg.add(paren.set_vbus_voltage_sensor(sens))
if conf := config.get(CONF_VSYS_VOLTAGE):
sens = await sensor.new_sensor(conf)
cg.add(paren.set_vsys_voltage_sensor(sens))
if conf := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(conf)
cg.add(paren.set_die_temperature_sensor(sens))

View File

@@ -0,0 +1,52 @@
"""Switch support for AXP2101 power rail control."""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import CONF_ID
from .. import AXP2101Component, axp2101_ns
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["axp2101"]
CONF_AXP2101_ID = "axp2101_id"
CONF_POWER_RAIL = "power_rail"
AXP2101Switch = axp2101_ns.class_("AXP2101Switch", switch.Switch, cg.Component)
# Power rail enum matching the PowerRail enum in C++
PowerRail = axp2101_ns.enum("PowerRail")
POWER_RAILS = {
"DCDC1": PowerRail.DCDC1,
"DCDC2": PowerRail.DCDC2,
"DCDC3": PowerRail.DCDC3,
"DCDC4": PowerRail.DCDC4,
"DCDC5": PowerRail.DCDC5,
"ALDO1": PowerRail.ALDO1,
"ALDO2": PowerRail.ALDO2,
"ALDO3": PowerRail.ALDO3,
"ALDO4": PowerRail.ALDO4,
"BLDO1": PowerRail.BLDO1,
"BLDO2": PowerRail.BLDO2,
"CPUSLDO": PowerRail.CPUSLDO,
"DLDO1": PowerRail.DLDO1,
"DLDO2": PowerRail.DLDO2,
}
CONFIG_SCHEMA = switch.switch_schema(AXP2101Switch).extend(
{
cv.GenerateID(CONF_AXP2101_ID): cv.use_id(AXP2101Component),
cv.Required(CONF_POWER_RAIL): cv.enum(POWER_RAILS, upper=True),
}
)
async def to_code(config):
"""Generate code for AXP2101 switch."""
paren = await cg.get_variable(config[CONF_AXP2101_ID])
var = await switch.new_switch(config)
await cg.register_component(var, config)
cg.add(var.set_parent(paren))
cg.add(var.set_power_rail(config[CONF_POWER_RAIL]))

View File

@@ -0,0 +1,74 @@
/**
* @file axp2101_switch.cpp
* @brief Implementation of AXP2101 switch component
*/
#include "axp2101_switch.h"
#include "esphome/core/log.h"
namespace esphome {
namespace axp2101 {
static const char *const TAG = "axp2101.switch";
static const char *power_rail_to_string(PowerRail rail) {
switch (rail) {
case DCDC1:
return "DCDC1";
case DCDC2:
return "DCDC2";
case DCDC3:
return "DCDC3";
case DCDC4:
return "DCDC4";
case DCDC5:
return "DCDC5";
case ALDO1:
return "ALDO1";
case ALDO2:
return "ALDO2";
case ALDO3:
return "ALDO3";
case ALDO4:
return "ALDO4";
case BLDO1:
return "BLDO1";
case BLDO2:
return "BLDO2";
case CPUSLDO:
return "CPUSLDO";
case DLDO1:
return "DLDO1";
case DLDO2:
return "DLDO2";
default:
return "UNKNOWN";
}
}
void AXP2101Switch::dump_config() {
LOG_SWITCH("", "AXP2101 Switch", this);
ESP_LOGCONFIG(TAG, " Power Rail: %s", power_rail_to_string(this->rail_));
}
void AXP2101Switch::write_state(bool state) {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
return;
}
bool success;
if (state) {
success = this->parent_->enable_power_rail(this->rail_);
} else {
success = this->parent_->disable_power_rail(this->rail_);
}
if (success) {
this->publish_state(state);
} else {
ESP_LOGE(TAG, "Failed to %s power rail %s", state ? "enable" : "disable", power_rail_to_string(this->rail_));
}
}
} // namespace axp2101
} // namespace esphome

View File

@@ -0,0 +1,34 @@
/**
* @file axp2101_switch.h
* @brief Switch component for AXP2101 power rail control
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
#include "../axp2101.h"
namespace esphome {
namespace axp2101 {
/**
* @brief Switch component for controlling AXP2101 power rails
*
* This switch allows enabling/disabling individual power rails (DCDCs and LDOs).
*/
class AXP2101Switch : public switch_::Switch, public Component {
public:
void set_parent(AXP2101Component *parent) { this->parent_ = parent; }
void set_power_rail(PowerRail rail) { this->rail_ = rail; }
void dump_config() override;
protected:
void write_state(bool state) override;
AXP2101Component *parent_;
PowerRail rail_;
};
} // namespace axp2101
} // namespace esphome

View File

@@ -70,9 +70,6 @@ void BME68xBSEC2Component::dump_config() {
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_,
this->bme68x_status_);
if (this->bsec_status_ == BSEC_I_SU_SUBSCRIBEDOUTPUTGATES) {
ESP_LOGE(TAG, "No sensors, add at least one sensor to the config");
}
}
if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) {

View File

@@ -72,16 +72,6 @@ def _final_validate(config: ConfigType) -> ConfigType:
"Add 'ap:' to your WiFi configuration to enable the captive portal."
)
# Register socket needs for DNS server and additional HTTP connections
# - 1 UDP socket for DNS server
# - 3 additional TCP sockets for captive portal detection probes + configuration requests
# OS captive portal detection makes multiple probe requests that stay in TIME_WAIT.
# Need headroom for actual user configuration requests.
# LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts.
from esphome.components import socket
socket.consume_sockets(4, "captive_portal")(config)
return config

View File

@@ -50,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
// Defer save to main loop thread to avoid NVS operations from HTTP thread
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect(ESPHOME_F("/?save"));
}
@@ -63,12 +63,6 @@ void CaptivePortal::start() {
this->base_->init();
if (!this->initialized_) {
this->base_->add_handler(this);
#ifdef USE_ESP32
// Enable LRU socket purging to handle captive portal detection probe bursts
// OS captive portal detection makes many simultaneous HTTP requests which can
// exhaust sockets. LRU purging automatically closes oldest idle connections.
this->base_->get_server()->set_lru_purge_enable(true);
#endif
}
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();

View File

@@ -40,10 +40,6 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void end() {
this->active_ = false;
this->disable_loop(); // Stop processing DNS requests
#ifdef USE_ESP32
// Disable LRU socket purging now that captive portal is done
this->base_->get_server()->set_lru_purge_enable(false);
#endif
this->base_->deinit();
if (this->dns_server_ != nullptr) {
this->dns_server_->stop();

View File

@@ -931,12 +931,6 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2:
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-Wno-nonnull-compare")
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)

View File

@@ -20,10 +20,6 @@ CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout"
# Default WiFi timeout - aligned with WiFi component ap_timeout
# Allows sufficient time to try all BSSIDs before starting provisioning mode
DEFAULT_WIFI_TIMEOUT = "90s"
improv_ns = cg.esphome_ns.namespace("improv")
Error = improv_ns.enum("Error")
@@ -63,7 +59,7 @@ CONFIG_SCHEMA = (
CONF_AUTHORIZED_DURATION, default="1min"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT
CONF_WIFI_TIMEOUT, default="1min"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
{

View File

@@ -127,7 +127,6 @@ void ESP32ImprovComponent::loop() {
// Set initial state based on whether we have an authorizer
this->set_state_(this->get_initial_state_(), false);
this->set_error_(improv::ERROR_NONE);
this->should_start_ = false; // Clear flag after starting
ESP_LOGD(TAG, "Service started!");
}
}

View File

@@ -45,7 +45,6 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
void start();
void stop();
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
bool should_start() const { return this->should_start_; }
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {

View File

@@ -13,6 +13,8 @@ namespace esphome {
namespace ld2410 {
static const char *const TAG = "ld2410";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
@@ -179,15 +181,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
}
void LD2410Component::dump_config() {
char mac_s[18];
char version_s[20];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ld24xx::format_version_str(this->version_, version_s);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2410:\n"
" Firmware version: %s\n"
" MAC address: %s",
version_s, mac_str);
version.c_str(), mac_str.c_str());
#ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
@@ -446,12 +448,12 @@ bool LD2410Component::handle_ack_data_() {
case CMD_QUERY_VERSION: {
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
char version_s[20];
ld24xx::format_version_str(this->version_, version_s);
ESP_LOGV(TAG, "Firmware version: %s", version_s);
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version_s);
this->version_text_sensor_->publish_state(version);
}
#endif
break;
@@ -504,9 +506,9 @@ bool LD2410Component::handle_ack_data_() {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
char mac_s[18];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ESP_LOGV(TAG, "MAC address: %s", mac_str);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str);

View File

@@ -14,6 +14,8 @@ namespace esphome {
namespace ld2412 {
static const char *const TAG = "ld2412";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
@@ -198,15 +200,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
}
void LD2412Component::dump_config() {
char mac_s[18];
char version_s[20];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ld24xx::format_version_str(this->version_, version_s);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2412:\n"
" Firmware version: %s\n"
" MAC address: %s",
version_s, mac_str);
version.c_str(), mac_str.c_str());
#ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "DynamicBackgroundCorrectionStatus",
@@ -490,12 +492,12 @@ bool LD2412Component::handle_ack_data_() {
case CMD_QUERY_VERSION: {
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
char version_s[20];
ld24xx::format_version_str(this->version_, version_s);
ESP_LOGV(TAG, "Firmware version: %s", version_s);
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version_s);
this->version_text_sensor_->publish_state(version);
}
#endif
break;
@@ -542,9 +544,9 @@ bool LD2412Component::handle_ack_data_() {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
char mac_s[18];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ESP_LOGV(TAG, "MAC address: %s", mac_str);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str);

View File

@@ -17,6 +17,8 @@ namespace esphome {
namespace ld2450 {
static const char *const TAG = "ld2450";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
@@ -190,15 +192,15 @@ void LD2450Component::setup() {
}
void LD2450Component::dump_config() {
char mac_s[18];
char version_s[20];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ld24xx::format_version_str(this->version_, version_s);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2450:\n"
" Firmware version: %s\n"
" MAC address: %s",
version_s, mac_str);
version.c_str(), mac_str.c_str());
#ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
@@ -640,12 +642,12 @@ bool LD2450Component::handle_ack_data_() {
case CMD_QUERY_VERSION: {
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
char version_s[20];
ld24xx::format_version_str(this->version_, version_s);
ESP_LOGV(TAG, "Firmware version: %s", version_s);
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version_s);
this->version_text_sensor_->publish_state(version);
}
#endif
break;
@@ -661,9 +663,9 @@ bool LD2450Component::handle_ack_data_() {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
char mac_s[18];
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
ESP_LOGV(TAG, "MAC address: %s", mac_str);
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str);

View File

@@ -1,12 +1,11 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include <memory>
#include <span>
#ifdef USE_SENSOR
#include "esphome/core/helpers.h"
#include "esphome/components/sensor/sensor.h"
#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
@@ -40,27 +39,6 @@
namespace esphome {
namespace ld24xx {
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
// Helper function to format MAC address with stack allocation
// Returns pointer to UNKNOWN_MAC constant or formatted buffer
// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator)
inline const char *format_mac_str(const uint8_t *mac_address, std::span<char, 18> buffer) {
if (mac_address_is_valid(mac_address)) {
format_mac_addr_upper(mac_address, buffer.data());
return buffer.data();
}
return UNKNOWN_MAC;
}
// Helper function to format firmware version with stack allocation
// Buffer must be exactly 20 bytes (format: "x.xxXXXXXX" fits in 11 + null terminator, 20 for safety)
inline void format_version_str(const uint8_t *version, std::span<char, 20> buffer) {
snprintf(buffer.data(), buffer.size(), VERSION_FMT, version[1], version[0], version[5], version[4], version[3],
version[2]);
}
#ifdef USE_SENSOR
// Helper class to store a sensor with a deduplicator & publish state only when the value changes
template<typename T> class SensorWithDedup {

View File

@@ -261,10 +261,6 @@ async def component_to_code(config):
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
# LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack
# exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic,
# which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks
# (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent.
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
# force using arduino framework

View File

@@ -73,17 +73,17 @@ void SFA30Component::update() {
}
if (this->formaldehyde_sensor_ != nullptr) {
const float formaldehyde = static_cast<int16_t>(raw_data[0]) / 5.0f;
const float formaldehyde = raw_data[0] / 5.0f;
this->formaldehyde_sensor_->publish_state(formaldehyde);
}
if (this->humidity_sensor_ != nullptr) {
const float humidity = static_cast<int16_t>(raw_data[1]) / 100.0f;
const float humidity = raw_data[1] / 100.0f;
this->humidity_sensor_->publish_state(humidity);
}
if (this->temperature_sensor_ != nullptr) {
const float temperature = static_cast<int16_t>(raw_data[2]) / 200.0f;
const float temperature = raw_data[2] / 200.0f;
this->temperature_sensor_->publish_state(temperature);
}

View File

@@ -94,18 +94,6 @@ void AsyncWebServer::end() {
}
}
void AsyncWebServer::set_lru_purge_enable(bool enable) {
if (this->lru_purge_enable_ == enable) {
return; // No change needed
}
this->lru_purge_enable_ = enable;
// If server is already running, restart it with new config
if (this->server_) {
this->end();
this->begin();
}
}
void AsyncWebServer::begin() {
if (this->server_) {
this->end();
@@ -113,8 +101,6 @@ void AsyncWebServer::begin() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = this->port_;
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
config.lru_purge_enable = this->lru_purge_enable_;
if (httpd_start(&this->server_, &config) == ESP_OK) {
const httpd_uri_t handler_get = {
.uri = "",
@@ -256,7 +242,6 @@ void AsyncWebServerRequest::send(int code, const char *content_type, const char
void AsyncWebServerRequest::redirect(const std::string &url) {
httpd_resp_set_status(*this, "302 Found");
httpd_resp_set_hdr(*this, "Location", url.c_str());
httpd_resp_set_hdr(*this, "Connection", "close");
httpd_resp_send(*this, nullptr, 0);
}

View File

@@ -199,13 +199,9 @@ class AsyncWebServer {
return *handler;
}
void set_lru_purge_enable(bool enable);
httpd_handle_t get_server() { return this->server_; }
protected:
uint16_t port_{};
httpd_handle_t server_{};
bool lru_purge_enable_{false};
static esp_err_t request_handler(httpd_req_t *r);
static esp_err_t request_post_handler(httpd_req_t *r);
esp_err_t request_handler_(AsyncWebServerRequest *request) const;

View File

@@ -69,12 +69,6 @@ CONF_MIN_AUTH_MODE = "min_auth_mode"
# Limited to 127 because selected_sta_index_ is int8_t in C++
MAX_WIFI_NETWORKS = 127
# Default AP timeout - allows sufficient time to try all BSSIDs during initial connection
# After AP starts, WiFi scanning is skipped to avoid disrupting the AP, so we only
# get best-effort connection attempts. Longer timeout ensures we exhaust all options
# before falling back to AP mode. Aligned with improv wifi_timeout default.
DEFAULT_AP_TIMEOUT = "90s"
wifi_ns = cg.esphome_ns.namespace("wifi")
EAPAuth = wifi_ns.struct("EAPAuth")
ManualIP = wifi_ns.struct("ManualIP")
@@ -183,7 +177,7 @@ CONF_AP_TIMEOUT = "ap_timeout"
WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend(
{
cv.Optional(
CONF_AP_TIMEOUT, default=DEFAULT_AP_TIMEOUT
CONF_AP_TIMEOUT, default="1min"
): cv.positive_time_period_milliseconds,
}
)

View File

@@ -199,12 +199,7 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1;
/// Cooldown duration in milliseconds after adapter restart or repeated failures
/// Allows WiFi hardware to stabilize before next connection attempt
static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500;
/// Cooldown duration when fallback AP is active and captive portal may be running
/// Longer interval gives users time to configure WiFi without constant connection attempts
/// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown
static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000;
static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 1000;
static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) {
switch (phase) {
@@ -280,9 +275,7 @@ int8_t WiFiComponent::find_next_hidden_sta_(int8_t start_index) {
}
}
// If we didn't scan this cycle, treat all networks as potentially hidden
// Otherwise, only retry networks that weren't seen in the scan
if (!this->did_scan_this_cycle_ || !this->ssid_was_seen_in_scan_(sta.get_ssid())) {
if (!this->ssid_was_seen_in_scan_(sta.get_ssid())) {
ESP_LOGD(TAG, "Hidden candidate " LOG_SECRET("'%s'") " at index %d", sta.get_ssid().c_str(), static_cast<int>(i));
return static_cast<int8_t>(i);
}
@@ -424,6 +417,10 @@ void WiFiComponent::start() {
void WiFiComponent::restart_adapter() {
ESP_LOGW(TAG, "Restarting adapter");
this->wifi_mode_(false, {});
// Enter cooldown state to allow WiFi hardware to stabilize after restart
// Don't set retry_phase_ or num_retried_ here - state machine handles transitions
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
this->action_started_ = millis();
this->error_from_callback_ = false;
}
@@ -444,16 +441,7 @@ void WiFiComponent::loop() {
switch (this->state_) {
case WIFI_COMPONENT_STATE_COOLDOWN: {
this->status_set_warning(LOG_STR("waiting to reconnect"));
// Skip cooldown if new credentials were provided while connecting
if (this->skip_cooldown_next_cycle_) {
this->skip_cooldown_next_cycle_ = false;
this->check_connecting_finished();
break;
}
// Use longer cooldown when captive portal/improv is active to avoid disrupting user config
bool portal_active = this->is_captive_portal_active_() || this->is_esp32_improv_active_();
uint32_t cooldown_duration = portal_active ? WIFI_COOLDOWN_WITH_AP_ACTIVE_MS : WIFI_COOLDOWN_DURATION_MS;
if (now - this->action_started_ > cooldown_duration) {
if (now - this->action_started_ > WIFI_COOLDOWN_DURATION_MS) {
// After cooldown we either restarted the adapter because of
// a failure, or something tried to connect over and over
// so we entered cooldown. In both cases we call
@@ -507,8 +495,7 @@ void WiFiComponent::loop() {
#endif // USE_WIFI_AP
#ifdef USE_IMPROV
if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active() &&
!esp32_improv::global_improv_component->should_start()) {
if (esp32_improv::global_improv_component != nullptr && !esp32_improv::global_improv_component->is_active()) {
if (now - this->last_connected_ > esp32_improv::global_improv_component->get_wifi_timeout()) {
if (this->wifi_mode_(true, {}))
esp32_improv::global_improv_component->start();
@@ -618,8 +605,6 @@ void WiFiComponent::set_sta(const WiFiAP &ap) {
this->init_sta(1);
this->add_sta(ap);
this->selected_sta_index_ = 0;
// When new credentials are set (e.g., from improv), skip cooldown to retry immediately
this->skip_cooldown_next_cycle_ = true;
}
WiFiAP WiFiComponent::build_params_for_current_phase_() {
@@ -681,17 +666,6 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
sta.set_ssid(ssid);
sta.set_password(password);
this->set_sta(sta);
// Trigger connection attempt (exits cooldown if needed, no-op if already connecting/connected)
this->connect_soon_();
}
void WiFiComponent::connect_soon_() {
// Only trigger retry if we're in cooldown - if already connecting/connected, do nothing
if (this->state_ == WIFI_COMPONENT_STATE_COOLDOWN) {
ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials");
this->retry_connect();
}
}
void WiFiComponent::start_connecting(const WiFiAP &ap) {
@@ -989,7 +963,6 @@ void WiFiComponent::check_scanning_finished() {
return;
}
this->scan_done_ = false;
this->did_scan_this_cycle_ = true;
if (this->scan_result_.empty()) {
ESP_LOGW(TAG, "No networks found");
@@ -1256,16 +1229,9 @@ WiFiRetryPhase WiFiComponent::determine_next_phase_() {
return WiFiRetryPhase::RESTARTING_ADAPTER;
case WiFiRetryPhase::RESTARTING_ADAPTER:
// After restart, go back to explicit hidden if we went through it initially
if (this->went_through_explicit_hidden_phase_()) {
return WiFiRetryPhase::EXPLICIT_HIDDEN;
}
// Skip scanning when captive portal/improv is active to avoid disrupting AP
// Even passive scans can cause brief AP disconnections on ESP32
if (this->is_captive_portal_active_() || this->is_esp32_improv_active_()) {
return WiFiRetryPhase::RETRY_HIDDEN;
}
return WiFiRetryPhase::SCAN_CONNECTING;
// After restart, go back to explicit hidden if we went through it initially, otherwise scan
return this->went_through_explicit_hidden_phase_() ? WiFiRetryPhase::EXPLICIT_HIDDEN
: WiFiRetryPhase::SCAN_CONNECTING;
}
// Should never reach here
@@ -1353,12 +1319,6 @@ bool WiFiComponent::transition_to_phase_(WiFiRetryPhase new_phase) {
if (!this->is_captive_portal_active_() && !this->is_esp32_improv_active_()) {
this->restart_adapter();
}
// Clear scan flag - we're starting a new retry cycle
this->did_scan_this_cycle_ = false;
// Always enter cooldown after restart (or skip-restart) to allow stabilization
// Use extended cooldown when AP is active to avoid constant scanning that blocks DNS
this->state_ = WIFI_COMPONENT_STATE_COOLDOWN;
this->action_started_ = millis();
// Return true to indicate we should wait (go to COOLDOWN) instead of immediately connecting
return true;

View File

@@ -291,7 +291,6 @@ class WiFiComponent : public Component {
void set_passive_scan(bool passive);
void save_wifi_sta(const std::string &ssid, const std::string &password);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Setup WiFi interface.
@@ -425,8 +424,6 @@ class WiFiComponent : public Component {
return true;
}
void connect_soon_();
void wifi_loop_();
bool wifi_mode_(optional<bool> sta, optional<bool> ap);
bool wifi_sta_pre_setup_();
@@ -532,8 +529,6 @@ class WiFiComponent : public Component {
bool enable_on_boot_{true};
bool got_ipv4_address_{false};
bool keep_scan_results_{false};
bool did_scan_this_cycle_{false};
bool skip_cooldown_next_cycle_{false};
// Pointers at the end (naturally aligned)
Trigger<> *connect_trigger_{new Trigger<>()};

View File

@@ -36,30 +36,7 @@ class Framework(StrEnum):
class ThreadModel(StrEnum):
"""Threading model identifiers for ESPHome scheduler.
ESPHome currently uses three threading models based on platform capabilities:
SINGLE:
- Single-threaded platforms (ESP8266, RP2040)
- No RTOS task switching
- No concurrent access to scheduler data structures
- No atomics or locks required
- Minimal overhead
MULTI_NO_ATOMICS:
- Multi-threaded platforms without hardware atomic RMW support (e.g. LibreTiny BK7231N)
- Uses FreeRTOS or another RTOS with multiple tasks
- CPU lacks exclusive load/store instructions (ARM968E-S has no LDREX/STREX)
- std::atomic cannot provide lock-free RMW; libatomic is avoided to save flash (48 KB)
- Scheduler uses explicit FreeRTOS mutexes for synchronization
MULTI_ATOMICS:
- Multi-threaded platforms with hardware atomic RMW support (ESP32, Cortex-M, Host)
- CPU provides native atomic instructions (ESP32 S32C1I, ARM LDREX/STREX)
- std::atomic is used for lock-free synchronization
- Reduced contention and better performance
"""
"""Threading model identifiers for ESPHome scheduler."""
SINGLE = "ESPHOME_THREAD_SINGLE"
MULTI_NO_ATOMICS = "ESPHOME_THREAD_MULTI_NO_ATOMICS"

View File

@@ -154,8 +154,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
// For retries, check if there's a cancelled timeout first
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
(has_cancelled_timeout_in_container_locked_(this->items_, component, name_cstr, /* match_retry= */ true) ||
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_cstr, /* match_retry= */ true))) {
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) ||
has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) {
// Skip scheduling - the retry was cancelled
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr);
@@ -556,8 +556,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
#ifndef ESPHOME_THREAD_SINGLE
// Mark items in defer queue as cancelled (they'll be skipped when processed)
if (type == SchedulerItem::TIMEOUT) {
total_cancelled +=
this->mark_matching_items_removed_locked_(this->defer_queue_, component, name_cstr, type, match_retry);
total_cancelled += this->mark_matching_items_removed_(this->defer_queue_, component, name_cstr, type, match_retry);
}
#endif /* not ESPHOME_THREAD_SINGLE */
@@ -566,20 +565,19 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
// (removing the last element doesn't break heap structure)
if (!this->items_.empty()) {
auto &last_item = this->items_.back();
if (this->matches_item_locked_(last_item, component, name_cstr, type, match_retry)) {
if (this->matches_item_(last_item, component, name_cstr, type, match_retry)) {
this->recycle_item_(std::move(this->items_.back()));
this->items_.pop_back();
total_cancelled++;
}
// For other items in heap, we can only mark for removal (can't remove from middle of heap)
size_t heap_cancelled =
this->mark_matching_items_removed_locked_(this->items_, component, name_cstr, type, match_retry);
size_t heap_cancelled = this->mark_matching_items_removed_(this->items_, component, name_cstr, type, match_retry);
total_cancelled += heap_cancelled;
this->to_remove_ += heap_cancelled; // Track removals for heap items
}
// Cancel items in to_add_
total_cancelled += this->mark_matching_items_removed_locked_(this->to_add_, component, name_cstr, type, match_retry);
total_cancelled += this->mark_matching_items_removed_(this->to_add_, component, name_cstr, type, match_retry);
return total_cancelled > 0;
}

View File

@@ -243,18 +243,8 @@ class Scheduler {
}
// Helper function to check if item matches criteria for cancellation
// IMPORTANT: Must be called with scheduler lock held
inline bool HOT matches_item_locked_(const std::unique_ptr<SchedulerItem> &item, Component *component,
const char *name_cstr, SchedulerItem::Type type, bool match_retry,
bool skip_removed = true) const {
// THREAD SAFETY: Check for nullptr first to prevent LoadProhibited crashes. On multi-threaded
// platforms, items can be moved out of defer_queue_ during processing, leaving nullptr entries.
// PR #11305 added nullptr checks in callers (mark_matching_items_removed_locked_() and
// has_cancelled_timeout_in_container_locked_()), but this check provides defense-in-depth: helper
// functions should be safe regardless of caller behavior.
// Fixes: https://github.com/esphome/esphome/issues/11940
if (!item)
return false;
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
if (item->component != component || item->type != type || (skip_removed && item->remove) ||
(match_retry && !item->is_retry)) {
return false;
@@ -314,8 +304,8 @@ class Scheduler {
// SAFETY: Moving out the unique_ptr leaves a nullptr in the vector at defer_queue_front_.
// This is intentional and safe because:
// 1. The vector is only cleaned up by cleanup_defer_queue_locked_() at the end of this function
// 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_locked_
// and has_cancelled_timeout_in_container_locked_ in scheduler.h)
// 2. Any code iterating defer_queue_ MUST check for nullptr items (see mark_matching_items_removed_
// and has_cancelled_timeout_in_container_ in scheduler.h)
// 3. The lock protects concurrent access, but the nullptr remains until cleanup
item = std::move(this->defer_queue_[this->defer_queue_front_]);
this->defer_queue_front_++;
@@ -403,10 +393,10 @@ class Scheduler {
// Helper to mark matching items in a container as removed
// Returns the number of items marked for removal
// IMPORTANT: Must be called with scheduler lock held
// IMPORTANT: Caller must hold the scheduler lock before calling this function.
template<typename Container>
size_t mark_matching_items_removed_locked_(Container &container, Component *component, const char *name_cstr,
SchedulerItem::Type type, bool match_retry) {
size_t mark_matching_items_removed_(Container &container, Component *component, const char *name_cstr,
SchedulerItem::Type type, bool match_retry) {
size_t count = 0;
for (auto &item : container) {
// Skip nullptr items (can happen in defer_queue_ when items are being processed)
@@ -415,7 +405,7 @@ class Scheduler {
// the vector can still contain nullptr items from the processing loop. This check prevents crashes.
if (!item)
continue;
if (this->matches_item_locked_(item, component, name_cstr, type, match_retry)) {
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
// Mark item for removal (platform-specific)
this->set_item_removed_(item.get(), true);
count++;
@@ -425,10 +415,9 @@ class Scheduler {
}
// Template helper to check if any item in a container matches our criteria
// IMPORTANT: Must be called with scheduler lock held
template<typename Container>
bool has_cancelled_timeout_in_container_locked_(const Container &container, Component *component,
const char *name_cstr, bool match_retry) const {
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
bool match_retry) const {
for (const auto &item : container) {
// Skip nullptr items (can happen in defer_queue_ when items are being processed)
// The defer_queue_ uses index-based processing: items are std::moved out but left in the
@@ -437,8 +426,8 @@ class Scheduler {
if (!item)
continue;
if (is_item_removed_(item.get()) &&
this->matches_item_locked_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
/* skip_removed= */ false)) {
this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
/* skip_removed= */ false)) {
return true;
}
}

View File

@@ -0,0 +1,51 @@
axp2101:
- id: pmic
i2c_id: i2c_bus
address: 0x34
update_interval: 60s
sensor:
- platform: axp2101
axp2101_id: pmic
battery_voltage:
name: "Battery Voltage"
battery_level:
name: "Battery Level"
vbus_voltage:
name: "VBUS Voltage"
vsys_voltage:
name: "VSYS Voltage"
temperature:
name: "Die Temperature"
switch:
- platform: axp2101
axp2101_id: pmic
name: "DCDC1 Enable"
power_rail: DCDC1
- platform: axp2101
axp2101_id: pmic
name: "ALDO1 Enable"
power_rail: ALDO1
- platform: axp2101
axp2101_id: pmic
name: "BLDO1 Enable"
power_rail: BLDO1
number:
- platform: axp2101
axp2101_id: pmic
name: "DCDC1 Voltage"
power_rail: DCDC1
- platform: axp2101
axp2101_id: pmic
name: "DCDC2 Voltage"
power_rail: DCDC2
- platform: axp2101
axp2101_id: pmic
name: "ALDO1 Voltage"
power_rail: ALDO1
- platform: axp2101
axp2101_id: pmic
name: "CPUSLDO Voltage"
power_rail: CPUSLDO

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
<<: !include common.yaml

View File

@@ -0,0 +1,4 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
<<: !include common.yaml