Compare commits
29 Commits
2025.12.0b
...
2025.12.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b5cede5f9 | ||
|
|
c737033cc4 | ||
|
|
0194bfd9ea | ||
|
|
339399eb70 | ||
|
|
99f7e9aeb7 | ||
|
|
ebb6babb3d | ||
|
|
0922f240e0 | ||
|
|
c8fb694dcb | ||
|
|
6054685dae | ||
|
|
61ec3508ed | ||
|
|
086ec770ea | ||
|
|
b055f5b4bf | ||
|
|
726db746c8 | ||
|
|
1922455fa7 | ||
|
|
dc943d7e7a | ||
|
|
93e38f2608 | ||
|
|
3a888326d8 | ||
|
|
f0d0ea60a7 | ||
|
|
7ca11764ab | ||
|
|
3e38a5e630 | ||
|
|
636be92c97 | ||
|
|
195b1c6323 | ||
|
|
7e08092012 | ||
|
|
0ea5f2fd81 | ||
|
|
fa3d998c3d | ||
|
|
864aaeec01 | ||
|
|
9c88e44300 | ||
|
|
4d6a93f92d | ||
|
|
7216120bfd |
@@ -1 +1 @@
|
||||
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07
|
||||
5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.12.0b4
|
||||
PROJECT_NUMBER = 2025.12.3
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
|
||||
cv.frequency, cv.Range(min=45.0, max=66.0)
|
||||
cv.frequency, cv.float_range(min=45.0, max=66.0)
|
||||
),
|
||||
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,
|
||||
|
||||
@@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
|
||||
|
||||
AUTO_LOAD = ["bme68x_bsec2"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
|
||||
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(
|
||||
|
||||
@@ -160,41 +160,63 @@ HYST_LEVEL = {
|
||||
"High": HystLevel.HYST_LEVEL_HIGH,
|
||||
}
|
||||
|
||||
# Config key -> Validator mapping
|
||||
# Optional settings to generate setter calls for
|
||||
CONFIG_MAP = {
|
||||
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
|
||||
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
|
||||
CONF_DC_BLOCKING_FILTER: cv.boolean,
|
||||
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)),
|
||||
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
|
||||
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
|
||||
CONF_CHANNEL: cv.uint8_t,
|
||||
CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
|
||||
CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
|
||||
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8),
|
||||
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000),
|
||||
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False),
|
||||
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean,
|
||||
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False),
|
||||
CONF_MANCHESTER: cv.boolean,
|
||||
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
|
||||
CONF_SYNC1: cv.hex_uint8_t,
|
||||
CONF_SYNC0: cv.hex_uint8_t,
|
||||
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
|
||||
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
|
||||
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7),
|
||||
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False),
|
||||
CONF_LNA_PRIORITY: cv.boolean,
|
||||
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False),
|
||||
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False),
|
||||
CONF_FREEZE: cv.enum(FREEZE, upper=False),
|
||||
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
|
||||
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
|
||||
CONF_PACKET_MODE: cv.boolean,
|
||||
CONF_PACKET_LENGTH: cv.uint8_t,
|
||||
CONF_CRC_ENABLE: cv.boolean,
|
||||
CONF_WHITENING: cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
|
||||
cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum(
|
||||
RX_ATTENUATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)
|
||||
),
|
||||
cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=788000)
|
||||
),
|
||||
cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=58000, max=812000)
|
||||
),
|
||||
cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t,
|
||||
cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=405000)
|
||||
),
|
||||
cv.Optional(CONF_FSK_DEVIATION): cv.All(
|
||||
cv.frequency, cv.float_range(min=1500, max=381000)
|
||||
),
|
||||
cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8),
|
||||
cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000),
|
||||
cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum(
|
||||
MODULATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MANCHESTER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7),
|
||||
cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False),
|
||||
cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum(
|
||||
MAX_LNA_GAIN, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7),
|
||||
cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum(
|
||||
CARRIER_SENSE_REL_THR, upper=False
|
||||
),
|
||||
cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum(
|
||||
FILTER_LENGTH_FSK_MSK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum(
|
||||
FILTER_LENGTH_ASK_OOK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
|
||||
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
|
||||
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
|
||||
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_WHITENING, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +239,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
|
||||
}
|
||||
)
|
||||
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
|
||||
.extend(CONFIG_MAP)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True)),
|
||||
_validate_packet_mode,
|
||||
@@ -229,7 +251,8 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
for key in CONFIG_MAP:
|
||||
for opt in CONFIG_MAP:
|
||||
key = opt.schema
|
||||
if key in config:
|
||||
cg.add(getattr(var, f"set_{key}")(config[key]))
|
||||
|
||||
|
||||
@@ -98,25 +98,8 @@ CC1101Component::CC1101Component() {
|
||||
this->state_.LENGTH_CONFIG = 2;
|
||||
this->state_.FS_AUTOCAL = 1;
|
||||
|
||||
// Default Settings
|
||||
this->set_frequency(433920);
|
||||
this->set_if_frequency(153);
|
||||
this->set_filter_bandwidth(203);
|
||||
this->set_channel(0);
|
||||
this->set_channel_spacing(200);
|
||||
this->set_symbol_rate(5000);
|
||||
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
|
||||
this->set_carrier_sense_above_threshold(true);
|
||||
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
|
||||
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
|
||||
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
|
||||
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
|
||||
this->set_lna_priority(false);
|
||||
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
|
||||
|
||||
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
|
||||
memset(this->pa_table_, 0, sizeof(this->pa_table_));
|
||||
this->set_output_power(10.0f);
|
||||
}
|
||||
|
||||
void CC1101Component::setup() {
|
||||
@@ -186,14 +169,16 @@ void CC1101Component::loop() {
|
||||
}
|
||||
|
||||
// Read packet
|
||||
uint8_t payload_length;
|
||||
uint8_t payload_length, expected_rx;
|
||||
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
|
||||
this->read_(Register::FIFO, &payload_length, 1);
|
||||
expected_rx = payload_length + 1;
|
||||
} else {
|
||||
payload_length = this->state_.PKTLEN;
|
||||
expected_rx = payload_length;
|
||||
}
|
||||
if (payload_length == 0 || payload_length > 64) {
|
||||
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
|
||||
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
|
||||
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
@@ -203,13 +188,12 @@ void CC1101Component::loop() {
|
||||
this->packet_.resize(payload_length);
|
||||
this->read_(Register::FIFO, this->packet_.data(), payload_length);
|
||||
|
||||
// Read status and trigger
|
||||
uint8_t status[2];
|
||||
this->read_(Register::FIFO, status, 2);
|
||||
int8_t rssi_raw = static_cast<int8_t>(status[0]);
|
||||
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = status[1] & STATUS_LQI_MASK;
|
||||
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
|
||||
this->read_(Register::RSSI);
|
||||
this->read_(Register::LQI);
|
||||
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
||||
if (this->state_.CRC_EN == 0 || crc_ok) {
|
||||
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
|
||||
}
|
||||
@@ -633,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) {
|
||||
this->state_.GDO0_CFG = 0x01;
|
||||
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
|
||||
this->state_.FIFO_THR = 15;
|
||||
// Don't append status bytes to FIFO - we read from registers instead
|
||||
this->state_.APPEND_STATUS = 0;
|
||||
} else {
|
||||
// Configure GDO0 for serial data (async serial mode)
|
||||
this->state_.GDO0_CFG = 0x0D;
|
||||
}
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
this->write_(Register::PKTCTRL1);
|
||||
this->write_(Register::IOCFG0);
|
||||
this->write_(Register::FIFOTHR);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -308,13 +308,21 @@ bool ESP32BLE::ble_setup_() {
|
||||
bool ESP32BLE::ble_dismantle_() {
|
||||
esp_err_t err = esp_bluedroid_disable();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
|
||||
return false;
|
||||
// ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine
|
||||
if (err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Already disabled");
|
||||
}
|
||||
err = esp_bluedroid_deinit();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
|
||||
return false;
|
||||
// ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine
|
||||
if (err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Already deinitialized");
|
||||
}
|
||||
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
|
||||
@@ -212,17 +212,23 @@ extern ESP32BLE *global_ble;
|
||||
|
||||
template<typename... Ts> class BLEEnabledCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_ble->is_active(); }
|
||||
bool check(const Ts &...x) override { return global_ble != nullptr && global_ble->is_active(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEEnableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_ble->enable(); }
|
||||
void play(const Ts &...x) override {
|
||||
if (global_ble != nullptr)
|
||||
global_ble->enable();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEDisableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_ble->disable(); }
|
||||
void play(const Ts &...x) override {
|
||||
if (global_ble != nullptr)
|
||||
global_ble->disable();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
@@ -185,7 +185,10 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_();
|
||||
|
||||
void ESP32BLETracker::stop_scan_() {
|
||||
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
|
||||
// If scanner is already idle, there's nothing to stop - this is not an error
|
||||
if (this->scanner_state_ != ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Reset timeout state machine when stopping scan
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
from esphome.components.psram import DOMAIN as psram_domain
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=8e6, max=20e6)
|
||||
cv.frequency, cv.float_range(min=8e6, max=20e6)
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -352,6 +352,8 @@ async def to_code(config):
|
||||
cg.add_define("USE_CAMERA")
|
||||
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
|
||||
|
||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace esphome {
|
||||
namespace esp32_camera {
|
||||
|
||||
static const char *const TAG = "esp32_camera";
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
|
||||
#endif
|
||||
|
||||
/* ---------------- public API (derivated) ---------------- */
|
||||
void ESP32Camera::setup() {
|
||||
@@ -204,7 +207,20 @@ void ESP32Camera::loop() {
|
||||
}
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGV(TAG, "Got Image: len=%u", fb->len);
|
||||
#else
|
||||
// Initialize log time on first frame to ensure accurate interval measurement
|
||||
if (this->frame_count_ == 0) {
|
||||
this->last_log_time_ = now;
|
||||
}
|
||||
this->frame_count_++;
|
||||
if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) {
|
||||
ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000);
|
||||
this->last_log_time_ = now;
|
||||
this->frame_count_ = 0;
|
||||
}
|
||||
#endif
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_camera_image(this->current_image_);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,10 @@ class ESP32Camera : public camera::Camera {
|
||||
|
||||
uint32_t last_idle_request_{0};
|
||||
uint32_t last_update_{0};
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
uint32_t last_log_time_{0};
|
||||
uint16_t frame_count_{0};
|
||||
#endif
|
||||
#ifdef USE_I2C
|
||||
i2c::InternalI2CBus *i2c_bus_{nullptr};
|
||||
#endif // USE_I2C
|
||||
|
||||
@@ -16,7 +16,7 @@ def valid_pwm_pin(value):
|
||||
esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm")
|
||||
ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
|
||||
@@ -93,35 +93,35 @@ CONF_DOUBLE_BUFFER = "double_buffer"
|
||||
CONF_MIN_REFRESH_RATE = "min_refresh_rate"
|
||||
|
||||
# Map to hub75 library enums (in global namespace)
|
||||
ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True)
|
||||
Hub75ShiftDriver = cg.global_ns.enum("Hub75ShiftDriver", is_class=True)
|
||||
SHIFT_DRIVERS = {
|
||||
"GENERIC": ShiftDriver.GENERIC,
|
||||
"FM6126A": ShiftDriver.FM6126A,
|
||||
"ICN2038S": ShiftDriver.ICN2038S,
|
||||
"FM6124": ShiftDriver.FM6124,
|
||||
"MBI5124": ShiftDriver.MBI5124,
|
||||
"DP3246": ShiftDriver.DP3246,
|
||||
"GENERIC": Hub75ShiftDriver.GENERIC,
|
||||
"FM6126A": Hub75ShiftDriver.FM6126A,
|
||||
"ICN2038S": Hub75ShiftDriver.ICN2038S,
|
||||
"FM6124": Hub75ShiftDriver.FM6124,
|
||||
"MBI5124": Hub75ShiftDriver.MBI5124,
|
||||
"DP3246": Hub75ShiftDriver.DP3246,
|
||||
}
|
||||
|
||||
PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True)
|
||||
Hub75PanelLayout = cg.global_ns.enum("Hub75PanelLayout", is_class=True)
|
||||
PANEL_LAYOUTS = {
|
||||
"HORIZONTAL": PanelLayout.HORIZONTAL,
|
||||
"TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN,
|
||||
"TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN,
|
||||
"BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP,
|
||||
"BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP,
|
||||
"TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG,
|
||||
"TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG,
|
||||
"BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG,
|
||||
"BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG,
|
||||
"HORIZONTAL": Hub75PanelLayout.HORIZONTAL,
|
||||
"TOP_LEFT_DOWN": Hub75PanelLayout.TOP_LEFT_DOWN,
|
||||
"TOP_RIGHT_DOWN": Hub75PanelLayout.TOP_RIGHT_DOWN,
|
||||
"BOTTOM_LEFT_UP": Hub75PanelLayout.BOTTOM_LEFT_UP,
|
||||
"BOTTOM_RIGHT_UP": Hub75PanelLayout.BOTTOM_RIGHT_UP,
|
||||
"TOP_LEFT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_LEFT_DOWN_ZIGZAG,
|
||||
"TOP_RIGHT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_RIGHT_DOWN_ZIGZAG,
|
||||
"BOTTOM_LEFT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_LEFT_UP_ZIGZAG,
|
||||
"BOTTOM_RIGHT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG,
|
||||
}
|
||||
|
||||
ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True)
|
||||
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
||||
SCAN_PATTERNS = {
|
||||
"STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN,
|
||||
"FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH,
|
||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||
"FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH,
|
||||
}
|
||||
|
||||
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
|
||||
@@ -528,7 +528,7 @@ def _build_config_struct(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
add_idf_component(
|
||||
name="esphome/esp-hub75",
|
||||
ref="0.1.6",
|
||||
ref="0.1.7",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via defines
|
||||
|
||||
@@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
nrf52="100kHz",
|
||||
): cv.All(
|
||||
cv.frequency,
|
||||
cv.Range(min=0, min_included=False),
|
||||
cv.float_range(min=0, min_included=False),
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT): cv.All(
|
||||
cv.only_with_framework(["arduino", "esp-idf"]),
|
||||
|
||||
@@ -45,7 +45,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, min_included=False)
|
||||
),
|
||||
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_PHASE_ANGLE): cv.All(
|
||||
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
|
||||
|
||||
@@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, min_included=False)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
@@ -96,13 +96,9 @@ class LValidator:
|
||||
return None
|
||||
if isinstance(value, Lambda):
|
||||
# Local import to avoid circular import
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
@@ -404,14 +404,9 @@ class TextValidator(LValidator):
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
# Local import to avoid circular import at module level
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
|
||||
if isinstance(value, dict):
|
||||
if format_str := value.get(CONF_FORMAT):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.config import Config
|
||||
@@ -200,6 +201,21 @@ class LvContext(LambdaContext):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
def get_lambda_context_args() -> list[tuple[SafeExpType, str]]:
|
||||
"""Get automation parameters from the current lambda context if available.
|
||||
|
||||
When called from outside LVGL's context (e.g., from interval),
|
||||
CodeContext.code_context will be None, so return empty args.
|
||||
"""
|
||||
if CodeContext.code_context is None:
|
||||
return []
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext base class doesn't define get_automation_parameters(),
|
||||
# but LambdaContext and LvContext (the concrete implementations) do.
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
return CodeContext.code_context.get_automation_parameters()
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
"""
|
||||
Create a local variable and enclose the code using it within a block.
|
||||
|
||||
@@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PCA9685Output),
|
||||
cv.Optional(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.Range(min=23.84, max=1525.88)
|
||||
cv.frequency, cv.float_range(min=23.84, max=1525.88)
|
||||
),
|
||||
cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum(
|
||||
|
||||
@@ -7,10 +7,10 @@ from esphome.const import (
|
||||
CONF_UPDATE_INTERVAL,
|
||||
DEVICE_CLASS_PM25,
|
||||
ICON_BLUR,
|
||||
SCHEDULER_DONT_RUN,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
)
|
||||
from esphome.core import TimePeriodMilliseconds
|
||||
|
||||
CODEOWNERS = ["@habbie"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
@@ -41,16 +41,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def validate_interval_uart(config):
|
||||
require_tx = False
|
||||
|
||||
interval = config.get(CONF_UPDATE_INTERVAL)
|
||||
|
||||
if isinstance(interval, TimePeriodMilliseconds):
|
||||
# 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects
|
||||
require_tx = True
|
||||
|
||||
uart.final_validate_device_schema(
|
||||
"pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx
|
||||
"pm1006",
|
||||
baud_rate=9600,
|
||||
require_rx=True,
|
||||
require_tx=interval.total_milliseconds != SCHEDULER_DONT_RUN,
|
||||
)(config)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"]
|
||||
rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm")
|
||||
RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
|
||||
@@ -188,7 +188,7 @@ class LWIPRawImpl : public Socket {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen);
|
||||
return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen);
|
||||
}
|
||||
std::string getpeername() override {
|
||||
if (pcb_ == nullptr) {
|
||||
|
||||
@@ -199,9 +199,13 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All(
|
||||
cv.hex_int, cv.Range(min=0, max=0xFFFF)
|
||||
),
|
||||
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
||||
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, max=100000)
|
||||
),
|
||||
cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
||||
cv.Required(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
|
||||
),
|
||||
cv.Required(CONF_HW_VERSION): cv.one_of(
|
||||
"sx1261", "sx1262", "sx1268", "llcc68", lower=True
|
||||
),
|
||||
|
||||
@@ -196,9 +196,13 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_BITSYNC): cv.boolean,
|
||||
cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE),
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
||||
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, max=100000)
|
||||
),
|
||||
cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
||||
cv.Required(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
|
||||
),
|
||||
cv.Required(CONF_MODULATION): cv.enum(MOD),
|
||||
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN),
|
||||
|
||||
@@ -34,7 +34,15 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t
|
||||
severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level];
|
||||
}
|
||||
int pri = this->facility_ * 8 + severity;
|
||||
auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S");
|
||||
auto now = this->time_->now();
|
||||
std::string timestamp;
|
||||
if (now.is_valid()) {
|
||||
timestamp = now.strftime("%b %e %H:%M:%S");
|
||||
} else {
|
||||
// RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of
|
||||
// obtaining system time.
|
||||
timestamp = "-";
|
||||
}
|
||||
size_t len = message_len;
|
||||
// remove color formatting
|
||||
if (this->strip_ && message[0] == 0x1B && len > 11) {
|
||||
|
||||
@@ -39,6 +39,7 @@ enum TemplateAlarmControlPanelRestoreMode {
|
||||
ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED,
|
||||
};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct SensorDataStore {
|
||||
bool last_chime_state;
|
||||
};
|
||||
@@ -49,7 +50,6 @@ struct SensorInfo {
|
||||
uint8_t store_index;
|
||||
};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct AlarmSensor {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
SensorInfo info;
|
||||
@@ -139,6 +139,9 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
FixedVector<AlarmSensor> sensors_;
|
||||
// a list of automatically bypassed sensors
|
||||
std::vector<uint8_t> bypassed_sensor_indicies_;
|
||||
// Per sensor data store
|
||||
std::vector<SensorDataStore> sensor_data_;
|
||||
uint8_t next_store_index_ = 0;
|
||||
#endif
|
||||
TemplateAlarmControlPanelRestoreMode restore_mode_{};
|
||||
|
||||
@@ -154,14 +157,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
uint32_t trigger_time_;
|
||||
// a list of codes
|
||||
std::vector<std::string> codes_;
|
||||
// Per sensor data store
|
||||
std::vector<SensorDataStore> sensor_data_;
|
||||
// requires a code to arm
|
||||
bool requires_code_to_arm_ = false;
|
||||
bool supports_arm_home_ = false;
|
||||
bool supports_arm_night_ = false;
|
||||
bool sensors_ready_ = false;
|
||||
uint8_t next_store_index_ = 0;
|
||||
// check if the code is valid
|
||||
bool is_code_valid_(optional<std::string> code);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/gpio_num.h"
|
||||
#include "soc/uart_pins.h"
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
@@ -139,6 +140,22 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
||||
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
|
||||
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||
|
||||
// Workaround for ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17459
|
||||
// Commit 9ed617fb17 removed gpio_func_sel() calls from uart_set_pin(), which breaks
|
||||
// UART on default UART0 pins that may have residual state from boot console.
|
||||
// Reset these pins before configuring UART to ensure they're in a clean state.
|
||||
if (tx == U0TXD_GPIO_NUM || tx == U0RXD_GPIO_NUM) {
|
||||
gpio_reset_pin(static_cast<gpio_num_t>(tx));
|
||||
}
|
||||
if (rx == U0TXD_GPIO_NUM || rx == U0RXD_GPIO_NUM) {
|
||||
gpio_reset_pin(static_cast<gpio_num_t>(rx));
|
||||
}
|
||||
|
||||
// Setup pins after reset to preserve open drain/pullup/pulldown flags
|
||||
auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
|
||||
if (!pin) {
|
||||
return;
|
||||
@@ -154,10 +171,6 @@ void IDFUARTComponent::load_settings(bool dump_config) {
|
||||
setup_pin_if_needed(this->tx_pin_);
|
||||
}
|
||||
|
||||
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
||||
int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
|
||||
int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
|
||||
|
||||
uint32_t invert = 0;
|
||||
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
|
||||
invert |= UART_SIGNAL_TXD_INV;
|
||||
|
||||
@@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
|
||||
config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
|
||||
global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// 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()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
|
||||
@@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// 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()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, notify IP listeners immediately as the IP is already configured
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
s_sta_had_ip = true;
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
} else if (!is_connected && s_sta_was_connected) {
|
||||
// Just disconnected
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.12.0b4"
|
||||
__version__ = "2025.12.3"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -27,3 +27,7 @@ dependencies:
|
||||
version: "1.7.6~1"
|
||||
rules:
|
||||
- if: "target in [esp32s2, esp32s3, esp32p4]"
|
||||
esphome/esp-hub75:
|
||||
version: 0.1.7
|
||||
rules:
|
||||
- if: "target in [esp32, esp32s2, esp32s3, esp32p4]"
|
||||
|
||||
@@ -99,14 +99,11 @@ def storage_should_clean(old: StorageJSON | None, new: StorageJSON) -> bool:
|
||||
|
||||
|
||||
def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool:
|
||||
if (
|
||||
# ESP32 uses CMake for both Arduino and ESP-IDF frameworks
|
||||
return (
|
||||
old.loaded_integrations != new.loaded_integrations
|
||||
or old.loaded_platforms != new.loaded_platforms
|
||||
) and new.core_platform == PLATFORM_ESP32:
|
||||
from esphome.components.esp32 import FRAMEWORK_ESP_IDF
|
||||
|
||||
return new.framework == FRAMEWORK_ESP_IDF
|
||||
return False
|
||||
) and new.core_platform == PLATFORM_ESP32
|
||||
|
||||
|
||||
def update_storage_json() -> None:
|
||||
|
||||
@@ -156,7 +156,6 @@ lib_deps =
|
||||
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
|
||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||
esphome/esp-audio-libs@2.0.1 ; audio
|
||||
esphome/esp-hub75@0.1.6 ; hub75
|
||||
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
@@ -180,7 +179,6 @@ lib_deps =
|
||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||
esphome/esp-audio-libs@2.0.1 ; audio
|
||||
esphome/esp-hub75@0.1.6 ; hub75
|
||||
build_flags =
|
||||
${common:idf.build_flags}
|
||||
-Wno-nonnull-compare
|
||||
|
||||
@@ -9,6 +9,13 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.storage_json import StorageJSON
|
||||
from esphome.writer import (
|
||||
@@ -21,6 +28,7 @@ from esphome.writer import (
|
||||
clean_build,
|
||||
clean_cmake_cache,
|
||||
storage_should_clean,
|
||||
storage_should_update_cmake_cache,
|
||||
update_storage_json,
|
||||
write_cpp,
|
||||
write_gitignore,
|
||||
@@ -164,6 +172,86 @@ def test_storage_edge_case_from_empty_integrations(
|
||||
assert storage_should_clean(old, new) is False
|
||||
|
||||
|
||||
# Tests for storage_should_update_cmake_cache
|
||||
|
||||
|
||||
@pytest.mark.parametrize("framework", ["arduino", "esp-idf"])
|
||||
def test_storage_should_update_cmake_cache_when_integration_added_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
framework: str,
|
||||
) -> None:
|
||||
"""Test cmake cache update triggered when integration added on ESP32."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework=framework,
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi", "restart"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework=framework,
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is True
|
||||
|
||||
|
||||
def test_storage_should_update_cmake_cache_when_platform_changed_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
) -> None:
|
||||
"""Test cmake cache update triggered when platforms change on ESP32."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
loaded_platforms={"sensor"},
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
loaded_platforms={"sensor", "binary_sensor"},
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is True
|
||||
|
||||
|
||||
def test_storage_should_not_update_cmake_cache_when_nothing_changes(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
) -> None:
|
||||
"""Test cmake cache not updated when nothing changes."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"core_platform",
|
||||
[PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_BK72XX, PLATFORM_RTL87XX],
|
||||
)
|
||||
def test_storage_should_not_update_cmake_cache_for_non_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
core_platform: str,
|
||||
) -> None:
|
||||
"""Test cmake cache not updated for non-ESP32 platforms."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=core_platform,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi", "restart"],
|
||||
core_platform=core_platform,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is False
|
||||
|
||||
|
||||
@patch("esphome.writer.clean_build")
|
||||
@patch("esphome.writer.StorageJSON")
|
||||
@patch("esphome.writer.storage_path")
|
||||
|
||||
Reference in New Issue
Block a user