mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 00:31:58 +00:00
Merge remote-tracking branch 'upstream/dev' into web_server_cap_portal_co_exist
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -9,12 +7,12 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-timer.h>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "hw_timer_esp_idf.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
static const char *const TAG = "ac_dimmer";
|
||||
|
||||
@@ -27,7 +25,14 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
|
||||
/// However other factors like gate driver propagation time
|
||||
/// are also considered and a really low value is not important
|
||||
/// See also: https://github.com/esphome/issues/issues/1632
|
||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
||||
static constexpr uint32_t GATE_ENABLE_TIME = 50;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
/// Timer frequency in Hz (1 MHz = 1µs resolution)
|
||||
static constexpr uint32_t TIMER_FREQUENCY_HZ = 1000000;
|
||||
/// Timer interrupt interval in microseconds
|
||||
static constexpr uint64_t TIMER_INTERVAL_US = 50;
|
||||
#endif
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
@@ -154,7 +159,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
#ifdef USE_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static HWTimer *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
@@ -194,15 +199,15 @@ void AcDimmer::setup() {
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// timer frequency of 1mhz
|
||||
dimmer_timer = timerBegin(1000000);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
|
||||
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarm(dimmer_timer, 50, true, 0);
|
||||
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
@@ -210,6 +215,7 @@ void AcDimmer::write_state(float state) {
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
this->store_.value = new_value;
|
||||
}
|
||||
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"AcDimmer:\n"
|
||||
@@ -230,7 +236,4 @@ void AcDimmer::dump_config() {
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||
|
||||
@@ -64,7 +61,4 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
DimMethod method_;
|
||||
};
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "hw_timer_esp_idf.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
|
||||
static const char *const TAG = "hw_timer_esp_idf";
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
// GPTimer divider constraints from ESP-IDF documentation
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MIN = 2;
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MAX = 65536;
|
||||
|
||||
using voidFuncPtr = void (*)();
|
||||
using voidFuncPtrArg = void (*)(void *);
|
||||
|
||||
struct InterruptConfigT {
|
||||
voidFuncPtr fn{nullptr};
|
||||
void *arg{nullptr};
|
||||
};
|
||||
|
||||
struct HWTimer {
|
||||
gptimer_handle_t timer_handle{nullptr};
|
||||
InterruptConfigT interrupt_handle{};
|
||||
bool timer_started{false};
|
||||
};
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency) {
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t counter_src_hz = 0;
|
||||
uint32_t divider = 0;
|
||||
soc_module_clk_t clk;
|
||||
for (auto clk_candidate : SOC_GPTIMER_CLKS) {
|
||||
clk = clk_candidate;
|
||||
esp_clk_tree_src_get_freq_hz(clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz);
|
||||
divider = counter_src_hz / frequency;
|
||||
if ((divider >= GPTIMER_DIVIDER_MIN) && (divider <= GPTIMER_DIVIDER_MAX)) {
|
||||
break;
|
||||
} else {
|
||||
divider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (divider == 0) {
|
||||
ESP_LOGE(TAG, "Resolution not possible; aborting");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gptimer_config_t config = {
|
||||
.clk_src = static_cast<gptimer_clock_source_t>(clk),
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = frequency,
|
||||
.flags = {.intr_shared = true},
|
||||
};
|
||||
|
||||
HWTimer *timer = new HWTimer();
|
||||
|
||||
err = gptimer_new_timer(&config, &timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer creation failed; error %d", err);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_enable(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer enable failed; error %d", err);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_start(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer start failed; error %d", err);
|
||||
gptimer_disable(timer->timer_handle);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
timer->timer_started = true;
|
||||
return timer;
|
||||
}
|
||||
|
||||
bool IRAM_ATTR timer_fn_wrapper(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args) {
|
||||
auto *isr = static_cast<InterruptConfigT *>(args);
|
||||
if (isr->fn) {
|
||||
if (isr->arg) {
|
||||
reinterpret_cast<voidFuncPtrArg>(isr->fn)(isr->arg);
|
||||
} else {
|
||||
isr->fn();
|
||||
}
|
||||
}
|
||||
// Return false to indicate that no higher-priority task was woken and no context switch is requested.
|
||||
return false;
|
||||
}
|
||||
|
||||
static void timer_attach_interrupt_functional_arg(HWTimer *timer, void (*user_func)(void *), void *arg) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = timer_fn_wrapper,
|
||||
};
|
||||
|
||||
timer->interrupt_handle.fn = reinterpret_cast<voidFuncPtr>(user_func);
|
||||
timer->interrupt_handle.arg = arg;
|
||||
|
||||
if (timer->timer_started) {
|
||||
gptimer_stop(timer->timer_handle);
|
||||
}
|
||||
gptimer_disable(timer->timer_handle);
|
||||
esp_err_t err = gptimer_register_event_callbacks(timer->timer_handle, &cbs, &timer->interrupt_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Attach Interrupt failed; error %d", err);
|
||||
}
|
||||
gptimer_enable(timer->timer_handle);
|
||||
if (timer->timer_started) {
|
||||
gptimer_start(timer->timer_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, voidFuncPtr user_func) {
|
||||
timer_attach_interrupt_functional_arg(timer, reinterpret_cast<voidFuncPtrArg>(user_func), nullptr);
|
||||
}
|
||||
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_alarm_config_t alarm_cfg = {
|
||||
.alarm_count = alarm_value,
|
||||
.reload_count = reload_count,
|
||||
.flags = {.auto_reload_on_alarm = autoreload},
|
||||
};
|
||||
esp_err_t err = gptimer_set_alarm_action(timer->timer_handle, &alarm_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Alarm Write failed; error %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
#endif
|
||||
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "driver/gptimer_types.h"
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
struct HWTimer;
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency);
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, void (*user_func)());
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
#endif
|
||||
@@ -32,7 +32,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -186,14 +186,17 @@ void APIServer::loop() {
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername()));
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
this->unregister_active_action_calls_for_connection(client.get());
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Save client info before removal for the trigger
|
||||
std::string client_name(client->get_name());
|
||||
std::string client_peername(client->get_peername());
|
||||
#endif
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
@@ -205,6 +208,11 @@ void APIServer::loop() {
|
||||
this->status_set_warning();
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Fire trigger after client is removed so api.connected reflects the true state
|
||||
this->client_disconnected_trigger_->trigger(client_name, client_peername);
|
||||
#endif
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,24 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
||||
}
|
||||
|
||||
/// Count number of varints in a packed buffer
|
||||
inline uint16_t count_packed_varints(const uint8_t *data, size_t len) {
|
||||
uint16_t count = 0;
|
||||
while (len > 0) {
|
||||
// Skip varint bytes until we find one without continuation bit
|
||||
while (len > 0 && (*data & 0x80)) {
|
||||
data++;
|
||||
len--;
|
||||
}
|
||||
if (len > 0) {
|
||||
data++;
|
||||
len--;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* StringRef Ownership Model for API Protocol Messages
|
||||
* ===================================================
|
||||
@@ -180,9 +198,10 @@ class ProtoVarInt {
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
// Forward declaration for decode_to_message and encode_to_writer
|
||||
class ProtoMessage;
|
||||
// Forward declarations for decode_to_message, encode_message and encode_packed_sint32
|
||||
class ProtoDecodableMessage;
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
@@ -334,6 +353,8 @@ class ProtoWriteBuffer {
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||
}
|
||||
/// Encode a packed repeated sint32 field (zero-copy from vector)
|
||||
void encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values);
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
@@ -341,9 +362,6 @@ class ProtoWriteBuffer {
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
@@ -792,8 +810,43 @@ class ProtoSize {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate size of a packed repeated sint32 field
|
||||
*/
|
||||
inline void add_packed_sint32(uint32_t field_id_size, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// field_id + length varint + packed data
|
||||
total_size_ += field_id_size + varint(static_cast<uint32_t>(packed_size)) + static_cast<uint32_t>(packed_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation of encode_packed_sint32 - must be after ProtoSize is defined
|
||||
inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
// Calculate packed size
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += ProtoSize::varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// Write tag (LENGTH_DELIMITED) + length + all zigzag-encoded values
|
||||
this->encode_field_raw(field_id, WIRE_TYPE_LENGTH_DELIMITED);
|
||||
this->encode_varint_raw(packed_size);
|
||||
for (int value : values) {
|
||||
this->encode_varint_raw(encode_zigzag32(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
|
||||
@@ -550,12 +550,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
ref="0.2.2",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via defines
|
||||
# Set compile-time configuration via build flags (so external library sees them)
|
||||
if CONF_BIT_DEPTH in config:
|
||||
cg.add_define("HUB75_BIT_DEPTH", config[CONF_BIT_DEPTH])
|
||||
cg.add_build_flag(f"-DHUB75_BIT_DEPTH={config[CONF_BIT_DEPTH]}")
|
||||
|
||||
if CONF_GAMMA_CORRECT in config:
|
||||
cg.add_define("HUB75_GAMMA_MODE", config[CONF_GAMMA_CORRECT])
|
||||
cg.add_build_flag(f"-DHUB75_GAMMA_MODE={config[CONF_GAMMA_CORRECT].enum_value}")
|
||||
|
||||
# Await all pin expressions
|
||||
pin_expressions = {
|
||||
|
||||
@@ -128,6 +128,39 @@ void RemoteReceiverBase::call_dumpers_() {
|
||||
|
||||
void RemoteReceiverBinarySensorBase::dump_config() { LOG_BINARY_SENSOR("", "Remote Receiver Binary Sensor", this); }
|
||||
|
||||
/* RemoteTransmitData */
|
||||
|
||||
void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count) {
|
||||
this->data_.clear();
|
||||
this->data_.reserve(count);
|
||||
|
||||
while (len > 0) {
|
||||
// Parse varint (inline, no dependency on api component)
|
||||
uint32_t raw = 0;
|
||||
uint32_t shift = 0;
|
||||
uint32_t consumed = 0;
|
||||
for (; consumed < len && consumed < 5; consumed++) {
|
||||
uint8_t byte = data[consumed];
|
||||
raw |= (byte & 0x7F) << shift;
|
||||
if ((byte & 0x80) == 0) {
|
||||
consumed++;
|
||||
break;
|
||||
}
|
||||
shift += 7;
|
||||
}
|
||||
if (consumed == 0)
|
||||
break; // Parse error
|
||||
|
||||
// Zigzag decode: (n >> 1) ^ -(n & 1)
|
||||
int32_t decoded = static_cast<int32_t>((raw >> 1) ^ (~(raw & 1) + 1));
|
||||
this->data_.push_back(decoded);
|
||||
data += consumed;
|
||||
len -= consumed;
|
||||
}
|
||||
}
|
||||
|
||||
/* RemoteTransmitterBase */
|
||||
|
||||
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
const auto &vec = this->temp_.get_data();
|
||||
|
||||
@@ -31,6 +31,11 @@ class RemoteTransmitData {
|
||||
uint32_t get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||
const RawTimings &get_data() const { return this->data_; }
|
||||
void set_data(const RawTimings &data) { this->data_ = data; }
|
||||
/// Set data from packed protobuf sint32 buffer (zigzag + varint encoded)
|
||||
/// @param data Pointer to packed zigzag-varint-encoded sint32 values
|
||||
/// @param len Length of the buffer in bytes
|
||||
/// @param count Number of values (for reserve optimization)
|
||||
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
||||
void reset() {
|
||||
this->data_.clear();
|
||||
this->carrier_frequency_ = 0;
|
||||
|
||||
@@ -502,14 +502,16 @@ static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, J
|
||||
|
||||
// Build id into stack buffer - ArduinoJson copies the string
|
||||
// Format: {prefix}/{device?}/{name}
|
||||
// Buffer size guaranteed by schema validation (NAME_MAX_LENGTH=120):
|
||||
// With devices: domain(20) + "/" + device(120) + "/" + name(120) + null = 263, rounded up to 280 for safety margin
|
||||
// Without devices: domain(20) + "/" + name(120) + null = 142, rounded up to 150 for safety margin
|
||||
// Buffer sizes use constants from entity_base.h validated in core/config.py
|
||||
// Note: Device name uses ESPHOME_FRIENDLY_NAME_MAX_LEN (sub-device max 120), not ESPHOME_DEVICE_NAME_MAX_LEN
|
||||
// (hostname)
|
||||
#ifdef USE_DEVICES
|
||||
char id_buf[280];
|
||||
static constexpr size_t ID_BUF_SIZE =
|
||||
ESPHOME_DOMAIN_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1;
|
||||
#else
|
||||
char id_buf[150];
|
||||
static constexpr size_t ID_BUF_SIZE = ESPHOME_DOMAIN_MAX_LEN + 1 + ESPHOME_FRIENDLY_NAME_MAX_LEN + 1;
|
||||
#endif
|
||||
char id_buf[ID_BUF_SIZE];
|
||||
char *p = id_buf;
|
||||
memcpy(p, prefix, prefix_len);
|
||||
p += prefix_len;
|
||||
|
||||
@@ -238,12 +238,21 @@ def _apply_min_auth_mode_default(config):
|
||||
def final_validate(config):
|
||||
has_sta = bool(config.get(CONF_NETWORKS, True))
|
||||
has_ap = CONF_AP in config
|
||||
has_improv = "esp32_improv" in fv.full_config.get()
|
||||
has_improv_serial = "improv_serial" in fv.full_config.get()
|
||||
full_config = fv.full_config.get()
|
||||
has_improv = "esp32_improv" in full_config
|
||||
has_improv_serial = "improv_serial" in full_config
|
||||
has_captive_portal = "captive_portal" in full_config
|
||||
has_web_server = "web_server" in full_config
|
||||
if not (has_sta or has_ap or has_improv or has_improv_serial):
|
||||
raise cv.Invalid(
|
||||
"Please specify at least an SSID or an Access Point to create."
|
||||
)
|
||||
if has_ap and not has_captive_portal and not has_web_server:
|
||||
_LOGGER.warning(
|
||||
"WiFi AP is configured but neither captive_portal nor web_server is enabled. "
|
||||
"The AP will not be usable for configuration or monitoring. "
|
||||
"Add 'captive_portal:' or 'web_server:' to your configuration."
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
@@ -466,7 +475,7 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
|
||||
cg.add_define("USE_WIFI_AP")
|
||||
elif CORE.is_esp32:
|
||||
elif CORE.is_esp32 and not CORE.using_arduino:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
||||
|
||||
|
||||
@@ -184,17 +184,24 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
|
||||
else:
|
||||
_compile_process_limit_default = cv.UNDEFINED
|
||||
|
||||
# Keep in sync with ESPHOME_FRIENDLY_NAME_MAX_LEN in esphome/core/entity_base.h
|
||||
FRIENDLY_NAME_MAX_LEN = 120
|
||||
|
||||
AREA_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Area),
|
||||
cv.Required(CONF_NAME): cv.All(cv.string_no_slash, cv.Length(max=120)),
|
||||
cv.Required(CONF_NAME): cv.All(
|
||||
cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Device),
|
||||
cv.Required(CONF_NAME): cv.All(cv.string_no_slash, cv.Length(max=120)),
|
||||
cv.Required(CONF_NAME): cv.All(
|
||||
cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN)
|
||||
),
|
||||
cv.Optional(CONF_AREA_ID): cv.use_id(Area),
|
||||
}
|
||||
)
|
||||
@@ -210,7 +217,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
# Keep max=120 in sync with OBJECT_ID_MAX_LEN in esphome/core/entity_base.h
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.All(
|
||||
cv.string_no_slash, cv.Length(max=120)
|
||||
cv.string_no_slash, cv.Length(max=FRIENDLY_NAME_MAX_LEN)
|
||||
),
|
||||
cv.Optional(CONF_AREA): validate_area_config,
|
||||
cv.Optional(CONF_COMMENT): cv.All(cv.string, cv.Length(max=255)),
|
||||
|
||||
@@ -16,7 +16,14 @@ namespace esphome {
|
||||
// Maximum device name length - keep in sync with validate_hostname() in esphome/core/config.py
|
||||
static constexpr size_t ESPHOME_DEVICE_NAME_MAX_LEN = 31;
|
||||
|
||||
// Maximum size for object_id buffer - keep in sync with friendly_name cv.Length(max=120) in esphome/core/config.py
|
||||
// Maximum friendly name length for entities and sub-devices - keep in sync with FRIENDLY_NAME_MAX_LEN in
|
||||
// esphome/core/config.py
|
||||
static constexpr size_t ESPHOME_FRIENDLY_NAME_MAX_LEN = 120;
|
||||
|
||||
// Maximum domain length (longest: "alarm_control_panel" = 19)
|
||||
static constexpr size_t ESPHOME_DOMAIN_MAX_LEN = 20;
|
||||
|
||||
// Maximum size for object_id buffer (friendly_name + null + margin)
|
||||
static constexpr size_t OBJECT_ID_MAX_LEN = 128;
|
||||
|
||||
enum EntityCategory : uint8_t {
|
||||
|
||||
@@ -11,7 +11,7 @@ pyserial==3.5
|
||||
platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==5.1.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20251013.0
|
||||
esphome-dashboard==20260110.0
|
||||
aioesphomeapi==43.10.1
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
substitutions:
|
||||
gate_pin: GPIO4
|
||||
zero_cross_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/ac_dimmer/test.esp32-idf.yaml
Normal file
5
tests/components/ac_dimmer/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
gate_pin: GPIO18
|
||||
zero_cross_pin: GPIO19
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -6,6 +6,7 @@ display:
|
||||
panel_height: 32
|
||||
double_buffer: true
|
||||
brightness: 128
|
||||
gamma_correct: gamma_2_2
|
||||
pages:
|
||||
- id: page1
|
||||
lambda: |-
|
||||
|
||||
@@ -5,6 +5,7 @@ display:
|
||||
panel_height: 32
|
||||
double_buffer: true
|
||||
brightness: 128
|
||||
gamma_correct: cie1931
|
||||
r1_pin: GPIO42
|
||||
g1_pin: GPIO41
|
||||
b1_pin: GPIO40
|
||||
|
||||
@@ -7,6 +7,7 @@ display:
|
||||
platform: ili9xxx
|
||||
id: main_lcd
|
||||
model: ili9342
|
||||
data_rate: 31.25MHz
|
||||
cs_pin: 20
|
||||
dc_pin: 21
|
||||
reset_pin: 22
|
||||
|
||||
@@ -42,4 +42,3 @@ status_led:
|
||||
|
||||
# Include API at priority 40
|
||||
api:
|
||||
password: "apipassword"
|
||||
|
||||
@@ -24,6 +24,14 @@ api:
|
||||
- logger.log:
|
||||
format: "Client %s disconnected from %s"
|
||||
args: [client_info.c_str(), client_address.c_str()]
|
||||
# Verify fix for issue #11131: api.connected should reflect true state in trigger
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
then:
|
||||
- logger.log: "Other clients still connected"
|
||||
else:
|
||||
- logger.log: "No clients remaining"
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
@@ -23,12 +23,14 @@ async def test_api_conditional_memory(
|
||||
# Track log messages
|
||||
connected_future = loop.create_future()
|
||||
disconnected_future = loop.create_future()
|
||||
no_clients_future = loop.create_future()
|
||||
service_simple_future = loop.create_future()
|
||||
service_args_future = loop.create_future()
|
||||
|
||||
# Patterns to match in logs
|
||||
connected_pattern = re.compile(r"Client .* connected from")
|
||||
disconnected_pattern = re.compile(r"Client .* disconnected from")
|
||||
no_clients_pattern = re.compile(r"No clients remaining")
|
||||
service_simple_pattern = re.compile(r"Simple service called")
|
||||
service_args_pattern = re.compile(
|
||||
r"Service called with: test_string, 123, 1, 42\.50"
|
||||
@@ -40,6 +42,8 @@ async def test_api_conditional_memory(
|
||||
connected_future.set_result(True)
|
||||
elif not disconnected_future.done() and disconnected_pattern.search(line):
|
||||
disconnected_future.set_result(True)
|
||||
elif not no_clients_future.done() and no_clients_pattern.search(line):
|
||||
no_clients_future.set_result(True)
|
||||
elif not service_simple_future.done() and service_simple_pattern.search(line):
|
||||
service_simple_future.set_result(True)
|
||||
elif not service_args_future.done() and service_args_pattern.search(line):
|
||||
@@ -109,3 +113,7 @@ async def test_api_conditional_memory(
|
||||
|
||||
# Client disconnected here, wait for disconnect log
|
||||
await asyncio.wait_for(disconnected_future, timeout=5.0)
|
||||
|
||||
# Verify fix for issue #11131: api.connected should be false in trigger
|
||||
# when the last client disconnects
|
||||
await asyncio.wait_for(no_clients_future, timeout=5.0)
|
||||
|
||||
Reference in New Issue
Block a user