mirror of
https://github.com/esphome/esphome.git
synced 2025-11-14 13:55:45 +00:00
Compare commits
67 Commits
ble_client
...
template_a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f408ce41c | ||
|
|
0afcf67c32 | ||
|
|
952bdfaac2 | ||
|
|
ed7e5cd325 | ||
|
|
a15f46e741 | ||
|
|
050a27a409 | ||
|
|
382483b063 | ||
|
|
1675408161 | ||
|
|
1d8b08dcce | ||
|
|
afed581079 | ||
|
|
ff107a0674 | ||
|
|
72da3d0f1e | ||
|
|
5a2e6697e0 | ||
|
|
799cfe1de4 | ||
|
|
6df0264d51 | ||
|
|
a859ecaad1 | ||
|
|
4f088c93c9 | ||
|
|
a1ab19d127 | ||
|
|
d869108416 | ||
|
|
2d6618da3c | ||
|
|
47fe84e922 | ||
|
|
735bf9930a | ||
|
|
769137fc09 | ||
|
|
3a5b3ad77d | ||
|
|
859101ddc9 | ||
|
|
29a50da635 | ||
|
|
5f0fa68d73 | ||
|
|
2f39b10baa | ||
|
|
5a550cc579 | ||
|
|
4b58cb4ce6 | ||
|
|
3872a2fd91 | ||
|
|
5d613ada83 | ||
|
|
9de80b635a | ||
|
|
748aee584a | ||
|
|
3cbfddcc83 | ||
|
|
1d71b6b93e | ||
|
|
398dba4fc8 | ||
|
|
298813d4fa | ||
|
|
56d141c741 | ||
|
|
47a7f729dd | ||
|
|
7806eb980f | ||
|
|
a59888224c | ||
|
|
58ad4759f0 | ||
|
|
87f79290ba | ||
|
|
9326d78439 | ||
|
|
a93887a790 | ||
|
|
d7fa131a8a | ||
|
|
79a4444928 | ||
|
|
572fae5c7d | ||
|
|
5dafaaced4 | ||
|
|
65a303d48f | ||
|
|
00c71b7236 | ||
|
|
ef04903a7a | ||
|
|
a2ec7f622c | ||
|
|
2f91e7bd47 | ||
|
|
80a7c6d3c3 | ||
|
|
7a92565a0c | ||
|
|
661920c51e | ||
|
|
a6b905e148 | ||
|
|
a6b7c1f18c | ||
|
|
7a700ca077 | ||
|
|
1539b43074 | ||
|
|
463a00b1ac | ||
|
|
82692d7053 | ||
|
|
1cccfdd2b9 | ||
|
|
855aa32f54 | ||
|
|
0f8332fe3c |
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
request-codeowner-reviews:
|
||||
name: Run
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request reviews from component codeowners
|
||||
|
||||
@@ -396,6 +396,7 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/runtime_stats/* @bdraco
|
||||
esphome/components/rx8130/* @beormund
|
||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
|
||||
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.11.0-dev
|
||||
PROJECT_NUMBER = 2025.12.0-dev
|
||||
|
||||
# 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
|
||||
|
||||
@@ -476,8 +476,9 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
auto *light = static_cast<light::LightState *>(entity);
|
||||
ListEntitiesLightResponse msg;
|
||||
auto traits = light->get_traits();
|
||||
auto supported_modes = traits.get_supported_color_modes();
|
||||
// Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values
|
||||
msg.supported_color_modes = &traits.get_supported_color_modes();
|
||||
msg.supported_color_modes = &supported_modes;
|
||||
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
@@ -1294,11 +1295,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
EventResponse resp;
|
||||
resp.set_event_type(StringRef(event_type));
|
||||
@@ -1650,9 +1651,7 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
||||
// O(n) but optimized for RAM and not performance.
|
||||
for (auto &item : items) {
|
||||
if (item.entity == entity && item.message_type == message_type) {
|
||||
// Clean up old creator before replacing
|
||||
item.creator.cleanup(message_type);
|
||||
// Move assign the new creator
|
||||
// Replace with new creator
|
||||
item.creator = std::move(creator);
|
||||
return;
|
||||
}
|
||||
@@ -1822,7 +1821,7 @@ void APIConnection::process_batch_() {
|
||||
|
||||
// Handle remaining items more efficiently
|
||||
if (items_processed < this->deferred_batch_.size()) {
|
||||
// Remove processed items from the beginning with proper cleanup
|
||||
// Remove processed items from the beginning
|
||||
this->deferred_batch_.remove_front(items_processed);
|
||||
// Reschedule for remaining items
|
||||
this->schedule_batch_();
|
||||
@@ -1835,10 +1834,10 @@ void APIConnection::process_batch_() {
|
||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single, uint8_t message_type) const {
|
||||
#ifdef USE_EVENT
|
||||
// Special case: EventResponse uses string pointer
|
||||
// Special case: EventResponse uses const char * pointer
|
||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
||||
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection {
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, const std::string &event_type);
|
||||
void send_event(event::Event *event, const char *event_type);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
@@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection {
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
||||
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
||||
#endif
|
||||
@@ -508,10 +508,8 @@ class APIConnection final : public APIServerConnection {
|
||||
// Constructor for function pointer
|
||||
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
||||
|
||||
// Constructor for string state capture
|
||||
explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
|
||||
|
||||
// No destructor - cleanup must be called explicitly with message_type
|
||||
// Constructor for const char * (Event types - no allocation needed)
|
||||
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
|
||||
|
||||
// Delete copy operations - MessageCreator should only be moved
|
||||
MessageCreator(const MessageCreator &other) = delete;
|
||||
@@ -523,8 +521,6 @@ class APIConnection final : public APIServerConnection {
|
||||
// Move assignment
|
||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||
if (this != &other) {
|
||||
// IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
|
||||
// In our usage, this happens in add_item() deduplication and vector::erase()
|
||||
data_ = other.data_;
|
||||
other.data_.function_ptr = nullptr;
|
||||
}
|
||||
@@ -535,20 +531,10 @@ class APIConnection final : public APIServerConnection {
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||
uint8_t message_type) const;
|
||||
|
||||
// Manual cleanup method - must be called before destruction for string types
|
||||
void cleanup(uint8_t message_type) {
|
||||
#ifdef USE_EVENT
|
||||
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
|
||||
delete data_.string_ptr;
|
||||
data_.string_ptr = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
union Data {
|
||||
MessageCreatorPtr function_ptr;
|
||||
std::string *string_ptr;
|
||||
const char *const_char_ptr;
|
||||
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
|
||||
};
|
||||
|
||||
@@ -568,42 +554,24 @@ class APIConnection final : public APIServerConnection {
|
||||
std::vector<BatchItem> items;
|
||||
uint32_t batch_start_time{0};
|
||||
|
||||
private:
|
||||
// Helper to cleanup items from the beginning
|
||||
void cleanup_items_(size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
items[i].creator.cleanup(items[i].message_type);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
DeferredBatch() {
|
||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||
items.reserve(8);
|
||||
}
|
||||
|
||||
~DeferredBatch() {
|
||||
// Ensure cleanup of any remaining items
|
||||
clear();
|
||||
}
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
// Add item to the front of the batch (for high priority messages like ping)
|
||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
|
||||
// Clear all items with proper cleanup
|
||||
// Clear all items
|
||||
void clear() {
|
||||
cleanup_items_(items.size());
|
||||
items.clear();
|
||||
batch_start_time = 0;
|
||||
}
|
||||
|
||||
// Remove processed items from the front with proper cleanup
|
||||
void remove_front(size_t count) {
|
||||
cleanup_items_(count);
|
||||
items.erase(items.begin(), items.begin() + count);
|
||||
}
|
||||
// Remove processed items from the front
|
||||
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
|
||||
|
||||
bool empty() const { return items.empty(); }
|
||||
size_t size() const { return items.size(); }
|
||||
@@ -682,21 +650,30 @@ class APIConnection final : public APIServerConnection {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Helper to check if a message type should bypass batching
|
||||
// Returns true if:
|
||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
||||
// the main loop is blocked, e.g., during OTA updates)
|
||||
// 2. It's an EventResponse (events are edge-triggered - every occurrence matters)
|
||||
// 3. OR: User has opted into immediate sending (should_try_send_immediately = true
|
||||
// AND batch_delay = 0)
|
||||
inline bool should_send_immediately_(uint8_t message_type) const {
|
||||
return (
|
||||
#ifdef USE_UPDATE
|
||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
message_type == EventResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0));
|
||||
}
|
||||
|
||||
// Helper method to send a message either immediately or via batching
|
||||
// Tries immediate send if should_send_immediately_() returns true and buffer has space
|
||||
// Falls back to batching if immediate send fails or isn't applicable
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
// Try to send immediately if:
|
||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
||||
// the main loop is blocked, e.g., during OTA updates)
|
||||
// 2. OR: We should try to send immediately (should_try_send_immediately = true)
|
||||
// AND Batch delay is 0 (user has opted in to immediate sending)
|
||||
// 3. AND: Buffer has space available
|
||||
if ((
|
||||
#ifdef USE_UPDATE
|
||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) &&
|
||||
this->helper_->can_write_without_blocking()) {
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
@@ -714,6 +691,27 @@ class APIConnection final : public APIServerConnection {
|
||||
return this->schedule_message_(entity, creator, message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Overload for MessageCreator (used by events which need to capture event_type)
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Try to send immediately if message type should bypass batching and buffer has space
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message in verbose mode
|
||||
this->log_proto_message_(entity, creator, message_type);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// If immediate send failed, fall through to batching
|
||||
}
|
||||
|
||||
// Fall back to scheduled batching
|
||||
return this->schedule_message_(entity, std::move(creator), message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Helper function to schedule a deferred message with known message type
|
||||
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
|
||||
|
||||
@@ -122,19 +122,16 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
void play_complex(const Ts &...x) override {
|
||||
this->num_running_++;
|
||||
this->var_ = std::make_tuple(x...);
|
||||
|
||||
bool result;
|
||||
std::vector<uint8_t> value;
|
||||
if (this->len_ >= 0) {
|
||||
// Static mode: write directly from flash pointer
|
||||
result = this->write(this->value_.data, this->len_);
|
||||
// Static mode: copy from flash to vector
|
||||
value.assign(this->value_.data, this->value_.data + this->len_);
|
||||
} else {
|
||||
// Template mode: call function and write the vector
|
||||
std::vector<uint8_t> value = this->value_.func(x...);
|
||||
result = this->write(value);
|
||||
// Template mode: call function
|
||||
value = this->value_.func(x...);
|
||||
}
|
||||
|
||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||
if (!result)
|
||||
if (!write(value))
|
||||
this->play_next_(x...);
|
||||
}
|
||||
|
||||
@@ -147,15 +144,15 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
* errors.
|
||||
*/
|
||||
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
|
||||
bool write(const uint8_t *data, size_t len) {
|
||||
bool write(const std::vector<uint8_t> &value) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
|
||||
return false;
|
||||
}
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
|
||||
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
|
||||
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
|
||||
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
|
||||
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_OK) {
|
||||
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
|
||||
return false;
|
||||
@@ -163,8 +160,6 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import web_server_base
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AP,
|
||||
CONF_ID,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
@@ -14,6 +17,10 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
@@ -50,6 +57,27 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
full_config = fv.full_config.get()
|
||||
wifi_conf = full_config.get("wifi")
|
||||
|
||||
if wifi_conf is None:
|
||||
# This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway
|
||||
raise cv.Invalid("Captive portal requires the wifi component to be configured")
|
||||
|
||||
if CONF_AP not in wifi_conf:
|
||||
_LOGGER.warning(
|
||||
"Captive portal is enabled but no WiFi AP is configured. "
|
||||
"The captive portal will not be accessible. "
|
||||
"Add 'ap:' to your WiFi configuration to enable the captive portal."
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL)
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
|
||||
@@ -49,9 +49,9 @@ void DebugComponent::dump_config() {
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
this->log_partition_info_(); // Log partition information for ESP32
|
||||
#endif // USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
this->log_partition_info_(); // Log partition information
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::loop() {
|
||||
|
||||
@@ -62,19 +62,19 @@ class DebugComponent : public PollingComponent {
|
||||
sensor::Sensor *cpu_frequency_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
/**
|
||||
* @brief Logs information about the device's partition table.
|
||||
*
|
||||
* This function iterates through the ESP32's partition table and logs details
|
||||
* This function iterates through the partition table and logs details
|
||||
* about each partition, including its name, type, subtype, starting address,
|
||||
* and size. The information is useful for diagnosing issues related to flash
|
||||
* memory or verifying the partition configuration dynamically at runtime.
|
||||
*
|
||||
* Only available when compiled for ESP32 platforms.
|
||||
* Only available when compiled for ESP32 and ZEPHYR platforms.
|
||||
*/
|
||||
void log_partition_info_();
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <cstdint>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
@@ -86,6 +87,37 @@ std::string DebugComponent::get_reset_reason_() {
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
|
||||
|
||||
static void fa_cb(const struct flash_area *fa, void *user_data) {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
const char *fa_label = flash_area_label(fa);
|
||||
|
||||
if (fa_label == nullptr) {
|
||||
fa_label = "-";
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s %-24.24s 0x%-10x 0x%-12x", (int) fa->fa_id,
|
||||
sizeof(uintptr_t) * 2, (uintptr_t) fa->fa_dev, fa->fa_dev->name, fa_label, (uint32_t) fa->fa_off,
|
||||
fa->fa_size);
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s 0x%-10x 0x%-12x", (int) fa->fa_id, sizeof(uintptr_t) * 2,
|
||||
(uintptr_t) fa->fa_dev, fa->fa_dev->name, (uint32_t) fa->fa_off, fa->fa_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::log_partition_info_() {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Label | Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "--------------------------------------------"
|
||||
"-----------------------------------------------");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "-----------------------------------------"
|
||||
"------------------------------");
|
||||
#endif
|
||||
flash_area_foreach(fa_cb, nullptr);
|
||||
}
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
std::string supply = "Main supply status: ";
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) {
|
||||
|
||||
@@ -23,7 +23,7 @@ void DS1307Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -334,12 +334,14 @@ def _is_framework_url(source: str) -> str:
|
||||
# - https://github.com/espressif/arduino-esp32/releases
|
||||
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(3, 3, 2),
|
||||
"latest": cv.Version(3, 3, 2),
|
||||
"dev": cv.Version(3, 3, 2),
|
||||
"latest": cv.Version(3, 3, 4),
|
||||
"dev": cv.Version(3, 3, 4),
|
||||
}
|
||||
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"),
|
||||
cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(3, 2, 0): cv.Version(54, 3, 20),
|
||||
@@ -357,8 +359,8 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"dev": cv.Version(5, 5, 1),
|
||||
}
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
||||
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
|
||||
@@ -373,14 +375,15 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
# The platform-espressif32 version
|
||||
# - https://github.com/pioarduino/platform-espressif32/releases
|
||||
PLATFORM_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(55, 3, 31, "1"),
|
||||
"latest": cv.Version(55, 3, 31, "1"),
|
||||
"dev": cv.Version(55, 3, 31, "1"),
|
||||
"recommended": cv.Version(55, 3, 31, "2"),
|
||||
"latest": cv.Version(55, 3, 31, "2"),
|
||||
"dev": cv.Version(55, 3, 31, "2"),
|
||||
}
|
||||
|
||||
|
||||
def _check_versions(value):
|
||||
value = value.copy()
|
||||
def _check_versions(config):
|
||||
config = config.copy()
|
||||
value = config[CONF_FRAMEWORK]
|
||||
|
||||
if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP:
|
||||
if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value:
|
||||
@@ -445,7 +448,7 @@ def _check_versions(value):
|
||||
"If there are connectivity or build issues please remove the manual version."
|
||||
)
|
||||
|
||||
return value
|
||||
return config
|
||||
|
||||
|
||||
def _parse_platform_version(value):
|
||||
@@ -495,6 +498,8 @@ def final_validate(config):
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
|
||||
errs = []
|
||||
conf_fw = config[CONF_FRAMEWORK]
|
||||
advanced = conf_fw[CONF_ADVANCED]
|
||||
full_config = fv.full_config.get()
|
||||
if pio_options := full_config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS):
|
||||
pio_flash_size_key = "board_upload.flash_size"
|
||||
@@ -511,22 +516,14 @@ def final_validate(config):
|
||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||
)
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] != VARIANT_ESP32
|
||||
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||
):
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_IGNORE_EFUSE_MAC_CRC}' is not supported on {config[CONF_VARIANT]}",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config.get(CONF_FRAMEWORK, {})
|
||||
.get(CONF_ADVANCED, {})
|
||||
.get(CONF_EXECUTE_FROM_PSRAM)
|
||||
):
|
||||
if advanced[CONF_EXECUTE_FROM_PSRAM]:
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32S3:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
@@ -542,6 +539,17 @@ def final_validate(config):
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
config[CONF_FLASH_SIZE] == "32MB"
|
||||
and "ota" in full_config
|
||||
and not advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]
|
||||
):
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"OTA with 32MB flash requires '{CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES}' to be set in the '{CONF_ADVANCED}' section of the esp32 configuration",
|
||||
path=[CONF_FLASH_SIZE],
|
||||
)
|
||||
)
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
@@ -596,89 +604,74 @@ def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
|
||||
FRAMEWORK_ESP_IDF = "esp-idf"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of(
|
||||
FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO
|
||||
),
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_RELEASE): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
||||
*LOG_LEVELS_IDF, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||
*ASSERTION_LEVELS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of(
|
||||
*COMPILER_OPTIMIZATIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean,
|
||||
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
|
||||
# it will handle disabling DHCP server when AP is not configured.
|
||||
# Default to false (disabled) when WiFi is not used.
|
||||
cv.OnlyWithout(
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_MDNS_QUERIES, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_SELECT, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
_check_versions,
|
||||
FRAMEWORK_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TYPE): cv.one_of(FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO),
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_RELEASE): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
||||
*LOG_LEVELS_IDF, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||
*ASSERTION_LEVELS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of(
|
||||
*COMPILER_OPTIMIZATIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
|
||||
# it will handle disabling DHCP server when AP is not configured.
|
||||
# Default to false (disabled) when WiFi is not used.
|
||||
cv.OnlyWithout(
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_MDNS_QUERIES, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -741,11 +734,11 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
|
||||
|
||||
|
||||
def _set_default_framework(config):
|
||||
config = config.copy()
|
||||
if CONF_FRAMEWORK not in config:
|
||||
config = config.copy()
|
||||
|
||||
variant = config[CONF_VARIANT]
|
||||
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
|
||||
if CONF_TYPE not in config[CONF_FRAMEWORK]:
|
||||
variant = config[CONF_VARIANT]
|
||||
if variant in ARDUINO_ALLOWED_VARIANTS:
|
||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
|
||||
_show_framework_migration_message(
|
||||
@@ -785,6 +778,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
_detect_variant,
|
||||
_set_default_framework,
|
||||
_check_versions,
|
||||
set_core_data,
|
||||
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
|
||||
)
|
||||
@@ -803,9 +797,7 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
|
||||
from esphome.components.socket import KEY_SOCKET_CONSUMERS
|
||||
|
||||
# Check if user manually specified CONFIG_LWIP_MAX_SOCKETS
|
||||
user_max_sockets = conf.get(CONF_SDKCONFIG_OPTIONS, {}).get(
|
||||
"CONFIG_LWIP_MAX_SOCKETS"
|
||||
)
|
||||
user_max_sockets = conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_LWIP_MAX_SOCKETS")
|
||||
|
||||
socket_consumers: dict[str, int] = CORE.data.get(KEY_SOCKET_CONSUMERS, {})
|
||||
total_sockets = sum(socket_consumers.values())
|
||||
@@ -975,23 +967,18 @@ async def to_code(config):
|
||||
# WiFi component handles its own optimization when AP mode is not used
|
||||
# When using Arduino with Ethernet, DHCP server functions must be available
|
||||
# for the Network library to compile, even if not actively used
|
||||
if (
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER in advanced
|
||||
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
|
||||
and not (
|
||||
conf[CONF_TYPE] == FRAMEWORK_ARDUINO
|
||||
and "ethernet" in CORE.loaded_integrations
|
||||
)
|
||||
if advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not (
|
||||
conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations
|
||||
):
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
||||
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
|
||||
if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
||||
if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
||||
|
||||
_configure_lwip_max_sockets(conf)
|
||||
|
||||
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
|
||||
if advanced[CONF_EXECUTE_FROM_PSRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
|
||||
|
||||
@@ -1002,23 +989,22 @@ async def to_code(config):
|
||||
# - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default)
|
||||
# - Up to 200% slower under load when all operations queue through tcpip_thread
|
||||
# Enabling this makes ESP-IDF socket performance match Arduino framework.
|
||||
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
|
||||
if advanced[CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
|
||||
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
|
||||
if advanced[CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True)
|
||||
|
||||
# Disable placing libc locks in IRAM to save RAM
|
||||
# This is safe for ESPHome since no IRAM ISRs (interrupts that run while cache is disabled)
|
||||
# use libc lock APIs. Saves approximately 1.3KB (1,356 bytes) of IRAM.
|
||||
if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True):
|
||||
if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
|
||||
|
||||
# Disable VFS support for termios (terminal I/O functions)
|
||||
# ESPHome doesn't use termios functions on ESP32 (only used in host UART driver).
|
||||
# Saves approximately 1.8KB of flash when disabled (default).
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True),
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS]
|
||||
)
|
||||
|
||||
# Disable VFS support for select() with file descriptors
|
||||
@@ -1032,8 +1018,7 @@ async def to_code(config):
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_SELECT",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True),
|
||||
"CONFIG_VFS_SUPPORT_SELECT", not advanced[CONF_DISABLE_VFS_SUPPORT_SELECT]
|
||||
)
|
||||
|
||||
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
|
||||
@@ -1046,8 +1031,7 @@ async def to_code(config):
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_DIR",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True),
|
||||
"CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR]
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
@@ -1061,7 +1045,7 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(flag, assertion_level == key)
|
||||
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
|
||||
compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION)
|
||||
compiler_optimization = advanced[CONF_COMPILER_OPTIMIZATION]
|
||||
for key, flag in COMPILER_OPTIMIZATIONS.items():
|
||||
add_idf_sdkconfig_option(flag, compiler_optimization == key)
|
||||
|
||||
@@ -1070,18 +1054,20 @@ async def to_code(config):
|
||||
conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT],
|
||||
)
|
||||
|
||||
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
|
||||
if advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False)
|
||||
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
|
||||
if advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]:
|
||||
_LOGGER.warning(
|
||||
"Using experimental features in ESP-IDF may result in unexpected failures."
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
|
||||
if config[CONF_FLASH_SIZE] == "32MB":
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
|
||||
)
|
||||
|
||||
cg.add_define(
|
||||
"ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE)
|
||||
)
|
||||
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
|
||||
|
||||
cg.add_define(
|
||||
"USE_ESP_IDF_VERSION_CODE",
|
||||
|
||||
@@ -634,11 +634,13 @@ void ESP32BLE::dump_config() {
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
char mac_s[18];
|
||||
format_mac_addr_upper(mac_address, mac_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BLE:\n"
|
||||
" MAC address: %s\n"
|
||||
" IO Capability: %s",
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||
mac_s, io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace esphome::esp32_ble_tracker {
|
||||
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
void set_addresses(const std::vector<uint64_t> &addresses) { this->address_vec_ = addresses; }
|
||||
void set_addresses(std::initializer_list<uint64_t> addresses) { this->address_vec_ = addresses; }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override {
|
||||
uint64_t u64_addr = device.address_uint64();
|
||||
|
||||
@@ -336,7 +336,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -381,7 +381,10 @@ void EthernetComponent::dump_config() {
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Ethernet:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Connected: %s",
|
||||
YESNO(this->is_connected()));
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -418,8 +421,6 @@ void EthernetComponent::dump_config() {
|
||||
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
|
||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
esp_netif_ip_info_t ip;
|
||||
|
||||
@@ -58,7 +58,6 @@ class EthernetComponent : public Component {
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
bool can_proceed() override;
|
||||
void on_powerdown() override { powerdown(); }
|
||||
bool is_connected();
|
||||
|
||||
|
||||
@@ -7,10 +7,8 @@ namespace homeassistant {
|
||||
static const char *const TAG = "homeassistant.time";
|
||||
|
||||
void HomeassistantTime::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Home Assistant Time:\n"
|
||||
" Timezone: '%s'",
|
||||
this->timezone_.c_str());
|
||||
ESP_LOGCONFIG(TAG, "Home Assistant Time");
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -231,7 +231,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -406,7 +406,7 @@ void LightCall::transform_parameters_() {
|
||||
}
|
||||
}
|
||||
ColorMode LightCall::compute_color_mode_() {
|
||||
const auto &supported_modes = this->parent_->get_traits().get_supported_color_modes();
|
||||
auto supported_modes = this->parent_->get_traits().get_supported_color_modes();
|
||||
int supported_count = supported_modes.size();
|
||||
|
||||
// Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown.
|
||||
|
||||
@@ -18,7 +18,8 @@ class LightTraits {
|
||||
public:
|
||||
LightTraits() = default;
|
||||
|
||||
const ColorModeMask &get_supported_color_modes() const { return this->supported_color_modes_; }
|
||||
// Return by value to avoid dangling reference when get_traits() returns a temporary
|
||||
ColorModeMask get_supported_color_modes() const { return this->supported_color_modes_; }
|
||||
void set_supported_color_modes(ColorModeMask supported_color_modes) {
|
||||
this->supported_color_modes_ = supported_color_modes;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import importlib
|
||||
import logging
|
||||
import pkgutil
|
||||
|
||||
from esphome.automation import build_automation, register_action, validate_automation
|
||||
from esphome.automation import build_automation, validate_automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
||||
from esphome.components.display import Display
|
||||
@@ -25,8 +27,8 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.helpers import write_file_if_changed
|
||||
|
||||
from . import defines as df, helpers, lv_validation as lvalid
|
||||
from .automation import disp_update, focused_widgets, refreshed_widgets, update_to_code
|
||||
from . import defines as df, helpers, lv_validation as lvalid, widgets
|
||||
from .automation import disp_update, focused_widgets, refreshed_widgets
|
||||
from .defines import add_define
|
||||
from .encoders import (
|
||||
ENCODERS_CONFIG,
|
||||
@@ -45,7 +47,6 @@ from .schemas import (
|
||||
WIDGET_TYPES,
|
||||
any_widget_schema,
|
||||
container_schema,
|
||||
create_modify_schema,
|
||||
obj_schema,
|
||||
)
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
@@ -54,7 +55,6 @@ from .trigger import add_on_boot_triggers, generate_triggers
|
||||
from .types import (
|
||||
FontEngine,
|
||||
IdleTrigger,
|
||||
ObjUpdateAction,
|
||||
PlainTrigger,
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
@@ -69,33 +69,23 @@ from .widgets import (
|
||||
set_obj_properties,
|
||||
styles_used,
|
||||
)
|
||||
from .widgets.animimg import animimg_spec
|
||||
from .widgets.arc import arc_spec
|
||||
from .widgets.button import button_spec
|
||||
from .widgets.buttonmatrix import buttonmatrix_spec
|
||||
from .widgets.canvas import canvas_spec
|
||||
from .widgets.checkbox import checkbox_spec
|
||||
from .widgets.container import container_spec
|
||||
from .widgets.dropdown import dropdown_spec
|
||||
from .widgets.img import img_spec
|
||||
from .widgets.keyboard import keyboard_spec
|
||||
from .widgets.label import label_spec
|
||||
from .widgets.led import led_spec
|
||||
from .widgets.line import line_spec
|
||||
from .widgets.lv_bar import bar_spec
|
||||
from .widgets.meter import meter_spec
|
||||
|
||||
# Import only what we actually use directly in this file
|
||||
from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code
|
||||
from .widgets.obj import obj_spec
|
||||
from .widgets.page import add_pages, generate_page_triggers, page_spec
|
||||
from .widgets.qrcode import qr_code_spec
|
||||
from .widgets.roller import roller_spec
|
||||
from .widgets.slider import slider_spec
|
||||
from .widgets.spinbox import spinbox_spec
|
||||
from .widgets.spinner import spinner_spec
|
||||
from .widgets.switch import switch_spec
|
||||
from .widgets.tabview import tabview_spec
|
||||
from .widgets.textarea import textarea_spec
|
||||
from .widgets.tileview import tileview_spec
|
||||
from .widgets.obj import obj_spec # Used in LVGL_SCHEMA
|
||||
from .widgets.page import ( # page_spec used in LVGL_SCHEMA
|
||||
add_pages,
|
||||
generate_page_triggers,
|
||||
page_spec,
|
||||
)
|
||||
|
||||
# Widget registration happens via WidgetType.__init__ in individual widget files
|
||||
# The imports below trigger creation of the widget types
|
||||
# Action registration (lvgl.{widget}.update) happens automatically
|
||||
# in the WidgetType.__init__ method
|
||||
|
||||
for module_info in pkgutil.iter_modules(widgets.__path__):
|
||||
importlib.import_module(f".widgets.{module_info.name}", package=__package__)
|
||||
|
||||
DOMAIN = "lvgl"
|
||||
DEPENDENCIES = ["display"]
|
||||
@@ -103,41 +93,6 @@ AUTO_LOAD = ["key_provider"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
for w_type in (
|
||||
label_spec,
|
||||
obj_spec,
|
||||
button_spec,
|
||||
bar_spec,
|
||||
slider_spec,
|
||||
arc_spec,
|
||||
line_spec,
|
||||
spinner_spec,
|
||||
led_spec,
|
||||
animimg_spec,
|
||||
checkbox_spec,
|
||||
img_spec,
|
||||
switch_spec,
|
||||
tabview_spec,
|
||||
buttonmatrix_spec,
|
||||
meter_spec,
|
||||
dropdown_spec,
|
||||
roller_spec,
|
||||
textarea_spec,
|
||||
spinbox_spec,
|
||||
keyboard_spec,
|
||||
tileview_spec,
|
||||
qr_code_spec,
|
||||
canvas_spec,
|
||||
container_spec,
|
||||
):
|
||||
WIDGET_TYPES[w_type.name] = w_type
|
||||
|
||||
for w_type in WIDGET_TYPES.values():
|
||||
register_action(
|
||||
f"lvgl.{w_type.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(w_type),
|
||||
)(update_to_code)
|
||||
|
||||
SIMPLE_TRIGGERS = (
|
||||
df.CONF_ON_PAUSE,
|
||||
@@ -376,7 +331,7 @@ async def to_code(configs):
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
cg.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
if "transform_angle" in styles_used:
|
||||
if {"transform_angle", "transform_zoom"} & styles_used:
|
||||
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||
for use in helpers.lv_uses:
|
||||
add_define(f"LV_USE_{use.upper()}")
|
||||
@@ -402,6 +357,15 @@ def add_hello_world(config):
|
||||
return config
|
||||
|
||||
|
||||
def _theme_schema(value):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||
for name, w in WIDGET_TYPES.items()
|
||||
}
|
||||
)(value)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validation
|
||||
|
||||
LVGL_SCHEMA = cv.All(
|
||||
@@ -454,12 +418,7 @@ LVGL_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
df.CONF_TRANSPARENCY_KEY, default=0x000400
|
||||
): lvalid.lv_color,
|
||||
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||
{
|
||||
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||
for name, w in WIDGET_TYPES.items()
|
||||
}
|
||||
),
|
||||
cv.Optional(df.CONF_THEME): _theme_schema,
|
||||
cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA,
|
||||
cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
|
||||
cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG,
|
||||
|
||||
@@ -411,6 +411,10 @@ def any_widget_schema(extras=None):
|
||||
Dynamically generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of
|
||||
widget under the widgets: key.
|
||||
|
||||
This uses lazy evaluation - the schema is built when called during validation,
|
||||
not at import time. This allows external components to register widgets
|
||||
before schema validation begins.
|
||||
|
||||
:param extras: Additional schema to be applied to each generated one
|
||||
:return: A validator for the Widgets key
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import sys
|
||||
|
||||
from esphome import automation, codegen as cg
|
||||
from esphome.automation import register_action
|
||||
from esphome.config_validation import Schema
|
||||
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
@@ -124,13 +126,16 @@ class WidgetType:
|
||||
schema=None,
|
||||
modify_schema=None,
|
||||
lv_name=None,
|
||||
is_mock: bool = False,
|
||||
):
|
||||
"""
|
||||
:param name: The widget name, e.g. "bar"
|
||||
:param w_type: The C type of the widget
|
||||
:param parts: What parts this widget supports
|
||||
:param schema: The config schema for defining a widget
|
||||
:param modify_schema: A schema to update the widget
|
||||
:param modify_schema: A schema to update the widget, defaults to the same as the schema
|
||||
:param lv_name: The name of the LVGL widget in the LVGL library, if different from the name
|
||||
:param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget
|
||||
"""
|
||||
self.name = name
|
||||
self.lv_name = lv_name or name
|
||||
@@ -146,6 +151,22 @@ class WidgetType:
|
||||
self.modify_schema = modify_schema
|
||||
self.mock_obj = MockObj(f"lv_{self.lv_name}", "_")
|
||||
|
||||
# Local import to avoid circular import
|
||||
from .automation import update_to_code
|
||||
from .schemas import WIDGET_TYPES, create_modify_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
create_modify_schema(self),
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
return False
|
||||
|
||||
@@ -213,17 +213,14 @@ class LvScrActType(WidgetType):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("lv_scr_act()", lv_obj_t, ())
|
||||
super().__init__("lv_scr_act()", lv_obj_t, (), is_mock=True)
|
||||
|
||||
async def to_code(self, w, config: dict):
|
||||
return []
|
||||
|
||||
|
||||
lv_scr_act_spec = LvScrActType()
|
||||
|
||||
|
||||
def get_scr_act(lv_comp: MockObj) -> Widget:
|
||||
return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {})
|
||||
return Widget.create(None, lv_comp.get_scr_act(), LvScrActType(), {})
|
||||
|
||||
|
||||
def get_widget_generator(wid):
|
||||
|
||||
@@ -2,7 +2,7 @@ from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
|
||||
|
||||
from ..automation import action_to_code, update_to_code
|
||||
from ..automation import action_to_code
|
||||
from ..defines import (
|
||||
CONF_CURSOR,
|
||||
CONF_DECIMAL_PLACES,
|
||||
@@ -171,17 +171,3 @@ async def spinbox_decrement(config, action_id, template_arg, args):
|
||||
lv.spinbox_decrement(w.obj)
|
||||
|
||||
return await action_to_code(widgets, do_increment, action_id, template_arg, args)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.spinbox.update",
|
||||
ObjUpdateAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
|
||||
cv.Required(CONF_VALUE): lv_float,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def spinbox_update_to_code(config, action_id, template_arg, args):
|
||||
return await update_to_code(config, action_id, template_arg, args)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -9,6 +11,13 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["mdns"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# High performance networking tracking infrastructure
|
||||
# Components can request high performance networking and this configures lwip and WiFi settings
|
||||
KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking"
|
||||
CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance"
|
||||
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
|
||||
@@ -47,6 +56,55 @@ def ip_address_literal(ip: str | int | None) -> cg.MockObj:
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
def require_high_performance_networking() -> None:
|
||||
"""Request high performance networking for network and WiFi.
|
||||
|
||||
Call this from components that need optimized network performance for streaming
|
||||
or high-throughput data transfer. This enables high performance mode which
|
||||
configures both lwip TCP settings and WiFi driver settings for improved
|
||||
network performance.
|
||||
|
||||
Settings applied (ESP-IDF only):
|
||||
- lwip: Larger TCP buffers, windows, and mailbox sizes
|
||||
- WiFi: Increased RX/TX buffers, AMPDU aggregation, PSRAM allocation (set by wifi component)
|
||||
|
||||
Configuration is PSRAM-aware:
|
||||
- With PSRAM guaranteed: Aggressive settings (512 RX buffers, 512KB TCP windows)
|
||||
- Without PSRAM: Conservative optimized settings (64 buffers, 65KB TCP windows)
|
||||
|
||||
Example:
|
||||
from esphome.components import network
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
...,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
"""
|
||||
# Only set up once (idempotent - multiple components can call this)
|
||||
if not CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False):
|
||||
CORE.data[KEY_HIGH_PERFORMANCE_NETWORKING] = True
|
||||
|
||||
|
||||
def has_high_performance_networking() -> bool:
|
||||
"""Check if high performance networking mode is enabled.
|
||||
|
||||
Returns True when high performance networking has been requested by a
|
||||
component or explicitly enabled in the network configuration. This indicates
|
||||
that lwip and WiFi will use optimized buffer sizes and settings.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to apply performance-related settings.
|
||||
|
||||
Returns:
|
||||
bool: True if high performance networking is enabled, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
@@ -71,6 +129,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(cv.boolean, cv.only_on_esp32),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -80,6 +139,70 @@ async def to_code(config):
|
||||
cg.add_define("USE_NETWORK")
|
||||
if CORE.using_arduino and CORE.is_esp32:
|
||||
cg.add_library("Networking", None)
|
||||
|
||||
# Apply high performance networking settings
|
||||
# Config can explicitly enable/disable, or default to component-driven behavior
|
||||
enable_high_perf = config.get(CONF_ENABLE_HIGH_PERFORMANCE)
|
||||
component_requested = CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
# Explicit config overrides component request
|
||||
should_enable = (
|
||||
enable_high_perf if enable_high_perf is not None else component_requested
|
||||
)
|
||||
|
||||
# Log when user explicitly disables but a component requested it
|
||||
if enable_high_perf is False and component_requested:
|
||||
_LOGGER.info(
|
||||
"High performance networking disabled by user configuration (overriding component request)"
|
||||
)
|
||||
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and should_enable:
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
if psram_guaranteed:
|
||||
_LOGGER.info(
|
||||
"Applying high-performance lwip settings (PSRAM guaranteed): 512KB TCP windows, 512 mailbox sizes"
|
||||
)
|
||||
# PSRAM is guaranteed - use aggressive settings
|
||||
# Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true
|
||||
# CONFIG_LWIP_WND_SCALE can only be enabled if CONFIG_SPIRAM_IGNORE_NOTFOUND isn't set
|
||||
# Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
|
||||
|
||||
# Enable window scaling for much larger TCP windows
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_WND_SCALE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RCV_SCALE", 3)
|
||||
|
||||
# Large TCP buffers and windows (requires PSRAM)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 512000)
|
||||
|
||||
# Large mailboxes for high throughput
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 512)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 512)
|
||||
|
||||
# TCP connection limits
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_ACTIVE_TCP", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_LISTENING_TCP", 16)
|
||||
|
||||
# TCP optimizations
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MAXRTX", 12)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SYNMAXRTX", 6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSS", 1436)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSL", 60000)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_OVERSIZE_MSS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_QUEUE_OOSEQ", True)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Applying optimized lwip settings: 65KB TCP windows, 64 mailbox sizes"
|
||||
)
|
||||
# PSRAM not guaranteed - use more conservative, but still optimized settings
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
|
||||
cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
|
||||
if enable_ipv6:
|
||||
|
||||
@@ -118,10 +118,10 @@ struct IPAddress {
|
||||
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
|
||||
#endif
|
||||
|
||||
bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); }
|
||||
bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
|
||||
@@ -25,6 +25,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_RESET_PIN,
|
||||
CONF_VOLTAGE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
@@ -102,6 +103,11 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
CONF_DCDC = "dcdc"
|
||||
CONF_REG0 = "reg0"
|
||||
CONF_UICR_ERASE = "uicr_erase"
|
||||
|
||||
VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
_detect_bootloader,
|
||||
@@ -116,6 +122,16 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DCDC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_REG0): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
cv.voltage,
|
||||
cv.one_of(*VOLTAGE_LEVELS, float=True),
|
||||
),
|
||||
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -182,6 +198,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if dfu_config := config.get(CONF_DFU):
|
||||
CORE.add_job(_dfu_to_code, dfu_config)
|
||||
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
|
||||
|
||||
if reg0_config := config.get(CONF_REG0):
|
||||
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])
|
||||
cg.add_define("USE_NRF52_REG0_VOUT", value)
|
||||
if reg0_config[CONF_UICR_ERASE]:
|
||||
cg.add_define("USE_NRF52_UICR_ERASE")
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
|
||||
121
esphome/components/nrf52/uicr.cpp
Normal file
121
esphome/components/nrf52/uicr.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_NRF52_REG0_VOUT
|
||||
#include <zephyr/init.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
extern "C" {
|
||||
void nvmc_config(uint32_t mode);
|
||||
void nvmc_wait();
|
||||
nrfx_err_t nrfx_nvmc_uicr_erase();
|
||||
}
|
||||
|
||||
namespace esphome::nrf52 {
|
||||
|
||||
enum class StatusFlags : uint8_t {
|
||||
OK = 0x00,
|
||||
NEED_RESET = 0x01,
|
||||
NEED_ERASE = 0x02,
|
||||
};
|
||||
|
||||
constexpr StatusFlags &operator|=(StatusFlags &a, StatusFlags b) {
|
||||
a = static_cast<StatusFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr bool operator&(StatusFlags a, StatusFlags b) {
|
||||
return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;
|
||||
}
|
||||
|
||||
static bool regout0_ok() {
|
||||
return (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) == (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
}
|
||||
|
||||
static StatusFlags set_regout0() {
|
||||
/* If the board is powered from USB (high voltage mode),
|
||||
* GPIO output voltage is set to 1.8 volts by default.
|
||||
*/
|
||||
if (!regout0_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->REGOUT0 =
|
||||
(NRF_UICR->REGOUT0 & ~((uint32_t) UICR_REGOUT0_VOUT_Msk)) | (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return regout0_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/6a9a6a3e6d0f86918e9286188426a279976645bd/lib/sdk11/components/libraries/bootloader_dfu/dfu_types.h#L61
|
||||
constexpr uint32_t BOOTLOADER_REGION_START = 0x000F4000;
|
||||
constexpr uint32_t BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS = 0x000FE000;
|
||||
|
||||
static bool bootloader_ok() {
|
||||
return NRF_UICR->NRFFW[0] == BOOTLOADER_REGION_START && NRF_UICR->NRFFW[1] == BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
}
|
||||
|
||||
static StatusFlags fix_bootloader() {
|
||||
if (!bootloader_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->NRFFW[0] = BOOTLOADER_REGION_START;
|
||||
NRF_UICR->NRFFW[1] = BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return bootloader_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
static StatusFlags set_uicr() {
|
||||
StatusFlags status = StatusFlags::OK;
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
if (BOOTLOADER_VERSION_REGISTER <= 0x902) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("cannot control regout0 for %#x\n", BOOTLOADER_VERSION_REGISTER);
|
||||
#endif
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
status |= set_regout0();
|
||||
}
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
status |= fix_bootloader();
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
static int board_esphome_init() {
|
||||
StatusFlags status = set_uicr();
|
||||
|
||||
#ifdef USE_NRF52_UICR_ERASE
|
||||
if (status & StatusFlags::NEED_ERASE) {
|
||||
nrfx_err_t ret = nrfx_nvmc_uicr_erase();
|
||||
if (ret != NRFX_SUCCESS) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("nrfx_nvmc_uicr_erase failed %d\n", ret);
|
||||
#endif
|
||||
} else {
|
||||
status |= set_uicr();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (status & StatusFlags::NEED_RESET) {
|
||||
/* a reset is required for changes to take effect */
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace esphome::nrf52
|
||||
|
||||
static int board_esphome_init() { return esphome::nrf52::board_esphome_init(); }
|
||||
|
||||
SYS_INIT(board_esphome_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
#endif
|
||||
@@ -23,7 +23,7 @@ void PCF85063Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -23,7 +23,7 @@ void PCF8563Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -35,6 +35,9 @@ DOMAIN = "psram"
|
||||
|
||||
DEPENDENCIES = [PLATFORM_ESP32]
|
||||
|
||||
# PSRAM availability tracking for cross-component coordination
|
||||
KEY_PSRAM_GUARANTEED = "psram_guaranteed"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
psram_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
@@ -71,6 +74,23 @@ def supported() -> bool:
|
||||
return variant in SPIRAM_MODES
|
||||
|
||||
|
||||
def is_guaranteed() -> bool:
|
||||
"""Check if PSRAM is guaranteed to be available.
|
||||
|
||||
Returns True when PSRAM is configured with both 'disabled: false' and
|
||||
'ignore_not_found: false', meaning the device will fail to boot if PSRAM
|
||||
is not found. This ensures safe use of high buffer configurations that
|
||||
depend on PSRAM.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to know PSRAM availability for configuration decisions.
|
||||
|
||||
Returns:
|
||||
bool: True if PSRAM is guaranteed, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_PSRAM_GUARANTEED, False)
|
||||
|
||||
|
||||
def validate_psram_mode(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if config[CONF_SPEED] == "120MHZ":
|
||||
@@ -131,7 +151,22 @@ def get_config_schema(config):
|
||||
|
||||
CONFIG_SCHEMA = get_config_schema
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_psram_mode
|
||||
|
||||
def _store_psram_guaranteed(config):
|
||||
"""Store PSRAM guaranteed status in CORE.data for other components.
|
||||
|
||||
PSRAM is "guaranteed" when it will fail if not found, ensuring safe use
|
||||
of high buffer configurations in network/wifi components.
|
||||
|
||||
Called during final validation to ensure the flag is available
|
||||
before any to_code() functions run.
|
||||
"""
|
||||
psram_guaranteed = not config[CONF_DISABLED] and not config[CONF_IGNORE_NOT_FOUND]
|
||||
CORE.data[KEY_PSRAM_GUARANTEED] = psram_guaranteed
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_psram_mode, _store_psram_guaranteed)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
0
esphome/components/rx8130/__init__.py
Normal file
0
esphome/components/rx8130/__init__.py
Normal file
128
esphome/components/rx8130/rx8130.cpp
Normal file
128
esphome/components/rx8130/rx8130.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "rx8130.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// https://download.epsondevice.com/td/pdf/app/RX8130CE_en.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace rx8130 {
|
||||
|
||||
static const uint8_t RX8130_REG_SEC = 0x10;
|
||||
static const uint8_t RX8130_REG_MIN = 0x11;
|
||||
static const uint8_t RX8130_REG_HOUR = 0x12;
|
||||
static const uint8_t RX8130_REG_WDAY = 0x13;
|
||||
static const uint8_t RX8130_REG_MDAY = 0x14;
|
||||
static const uint8_t RX8130_REG_MONTH = 0x15;
|
||||
static const uint8_t RX8130_REG_YEAR = 0x16;
|
||||
static const uint8_t RX8130_REG_EXTEN = 0x1C;
|
||||
static const uint8_t RX8130_REG_FLAG = 0x1D;
|
||||
static const uint8_t RX8130_REG_CTRL0 = 0x1E;
|
||||
static const uint8_t RX8130_REG_CTRL1 = 0x1F;
|
||||
static const uint8_t RX8130_REG_DIG_OFFSET = 0x30;
|
||||
static const uint8_t RX8130_BIT_CTRL_STOP = 0x40;
|
||||
static const uint8_t RX8130_BAT_FLAGS = 0x30;
|
||||
static const uint8_t RX8130_CLEAR_FLAGS = 0x00;
|
||||
|
||||
static const char *const TAG = "rx8130";
|
||||
|
||||
constexpr uint8_t bcd2dec(uint8_t val) { return (val >> 4) * 10 + (val & 0x0f); }
|
||||
constexpr uint8_t dec2bcd(uint8_t val) { return ((val / 10) << 4) + (val % 10); }
|
||||
|
||||
void RX8130Component::setup() {
|
||||
// Set digital offset to disabled with no offset
|
||||
if (this->write_register(RX8130_REG_DIG_OFFSET, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Disable wakeup timers
|
||||
if (this->write_register(RX8130_REG_EXTEN, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Clear VLF flag in case there has been data loss
|
||||
if (this->write_register(RX8130_REG_FLAG, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Clear test flag and disable interrupts
|
||||
if (this->write_register(RX8130_REG_CTRL0, &RX8130_CLEAR_FLAGS, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Enable battery charging and switching
|
||||
if (this->write_register(RX8130_REG_CTRL1, &RX8130_BAT_FLAGS, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Clear STOP bit
|
||||
this->stop_(false);
|
||||
}
|
||||
|
||||
void RX8130Component::update() { this->read_time(); }
|
||||
|
||||
void RX8130Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "RX8130:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
void RX8130Component::read_time() {
|
||||
uint8_t date[7];
|
||||
if (this->read_register(RX8130_REG_SEC, date, 7) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
ESPTime rtc_time{
|
||||
.second = bcd2dec(date[0] & 0x7f),
|
||||
.minute = bcd2dec(date[1] & 0x7f),
|
||||
.hour = bcd2dec(date[2] & 0x3f),
|
||||
.day_of_week = bcd2dec(date[3] & 0x7f),
|
||||
.day_of_month = bcd2dec(date[4] & 0x3f),
|
||||
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
||||
.month = bcd2dec(date[5] & 0x1f),
|
||||
.year = static_cast<uint16_t>(bcd2dec(date[6]) + 2000),
|
||||
.is_dst = false, // not used
|
||||
.timestamp = 0 // overwritten by recalc_timestamp_utc(false)
|
||||
};
|
||||
rtc_time.recalc_timestamp_utc(false);
|
||||
if (!rtc_time.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Read UTC time: %04d-%02d-%02d %02d:%02d:%02d", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
|
||||
rtc_time.hour, rtc_time.minute, rtc_time.second);
|
||||
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
|
||||
}
|
||||
|
||||
void RX8130Component::write_time() {
|
||||
auto now = time::RealTimeClock::utcnow();
|
||||
if (!now.is_valid()) {
|
||||
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
|
||||
return;
|
||||
}
|
||||
uint8_t buff[7];
|
||||
buff[0] = dec2bcd(now.second);
|
||||
buff[1] = dec2bcd(now.minute);
|
||||
buff[2] = dec2bcd(now.hour);
|
||||
buff[3] = dec2bcd(now.day_of_week);
|
||||
buff[4] = dec2bcd(now.day_of_month);
|
||||
buff[5] = dec2bcd(now.month);
|
||||
buff[6] = dec2bcd(now.year % 100);
|
||||
this->stop_(true);
|
||||
if (this->write_register(RX8130_REG_SEC, buff, 7) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Wrote UTC time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour,
|
||||
now.minute, now.second);
|
||||
}
|
||||
this->stop_(false);
|
||||
}
|
||||
|
||||
void RX8130Component::stop_(bool stop) {
|
||||
const uint8_t data = stop ? RX8130_BIT_CTRL_STOP : RX8130_CLEAR_FLAGS;
|
||||
if (this->write_register(RX8130_REG_CTRL0, &data, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rx8130
|
||||
} // namespace esphome
|
||||
35
esphome/components/rx8130/rx8130.h
Normal file
35
esphome/components/rx8130/rx8130.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace rx8130 {
|
||||
|
||||
class RX8130Component : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void read_time();
|
||||
void write_time();
|
||||
/// Ensure RTC is initialized at the correct time in the setup sequence
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
void stop_(bool stop);
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<RX8130Component> {
|
||||
public:
|
||||
void play(const Ts... x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<RX8130Component> {
|
||||
public:
|
||||
void play(const Ts... x) override { this->parent_->read_time(); }
|
||||
};
|
||||
|
||||
} // namespace rx8130
|
||||
} // namespace esphome
|
||||
56
esphome/components/rx8130/time.py
Normal file
56
esphome/components/rx8130/time.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, time
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@beormund"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
rx8130_ns = cg.esphome_ns.namespace("rx8130")
|
||||
RX8130Component = rx8130_ns.class_("RX8130Component", time.RealTimeClock, i2c.I2CDevice)
|
||||
WriteAction = rx8130_ns.class_("WriteAction", automation.Action)
|
||||
ReadAction = rx8130_ns.class_("ReadAction", automation.Action)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RX8130Component),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x32))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"rx8130.write_time",
|
||||
WriteAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(RX8130Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def rx8130_write_time_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"rx8130.read_time",
|
||||
ReadAction,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(RX8130Component),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def rx8130_read_time_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await time.register_time(var, config)
|
||||
@@ -61,6 +61,7 @@ void SNTPComponent::dump_config() {
|
||||
for (auto &server : this->servers_) {
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
|
||||
}
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
#if !defined(USE_ESP32)
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from esphome import automation, external_files
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_player, psram, speaker
|
||||
from esphome.components import audio, esp32, media_player, network, psram, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
@@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CODEOWNERS = ["@kahrendt", "@synesthesiam"]
|
||||
DOMAIN = "media_player"
|
||||
@@ -280,6 +281,18 @@ PIPELINE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
"""Request high performance networking for streaming media.
|
||||
|
||||
Speaker media player streams audio data, so it always benefits from
|
||||
optimized WiFi and lwip settings regardless of codec support.
|
||||
Called during config validation to ensure flags are set before to_code().
|
||||
"""
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_player.media_player_schema(SpeakerMediaPlayer).extend(
|
||||
{
|
||||
@@ -304,6 +317,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
_validate_repeated_speaker,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
|
||||
|
||||
@@ -321,28 +335,10 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]:
|
||||
# Compile all supported audio codecs and optimize the wifi settings
|
||||
|
||||
# Compile all supported audio codecs
|
||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT", True)
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT", True)
|
||||
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
# Allocate wifi buffers in PSRAM
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
var = await media_player.new_media_player(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -137,7 +137,11 @@ async def to_code(config):
|
||||
cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME]))
|
||||
supports_arm_night = True
|
||||
|
||||
for sensor in config.get(CONF_BINARY_SENSORS, []):
|
||||
if sensors := config.get(CONF_BINARY_SENSORS, []):
|
||||
# Initialize FixedVector with the exact number of sensors
|
||||
cg.add(var.init_sensors(len(sensors)))
|
||||
|
||||
for sensor in sensors:
|
||||
bs = await cg.get_variable(sensor[CONF_INPUT])
|
||||
|
||||
flags = BinarySensorFlags[FLAG_NORMAL]
|
||||
|
||||
@@ -20,10 +20,13 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor,
|
||||
// Save the flags and type. Assign a store index for the per sensor data type.
|
||||
SensorDataStore sd;
|
||||
sd.last_chime_state = false;
|
||||
this->sensor_map_[sensor].flags = flags;
|
||||
this->sensor_map_[sensor].type = type;
|
||||
AlarmSensor alarm_sensor;
|
||||
alarm_sensor.sensor = sensor;
|
||||
alarm_sensor.info.flags = flags;
|
||||
alarm_sensor.info.type = type;
|
||||
alarm_sensor.info.store_index = this->next_store_index_++;
|
||||
this->sensors_.push_back(alarm_sensor);
|
||||
this->sensor_data_.push_back(sd);
|
||||
this->sensor_map_[sensor].store_index = this->next_store_index_++;
|
||||
};
|
||||
|
||||
static const LogString *sensor_type_to_string(AlarmSensorType type) {
|
||||
@@ -45,7 +48,7 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"TemplateAlarmControlPanel:\n"
|
||||
" Current State: %s\n"
|
||||
" Number of Codes: %u\n"
|
||||
" Number of Codes: %zu\n"
|
||||
" Requires Code To Arm: %s\n"
|
||||
" Arming Away Time: %" PRIu32 "s\n"
|
||||
" Arming Home Time: %" PRIu32 "s\n"
|
||||
@@ -58,7 +61,8 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
(this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
|
||||
(this->trigger_time_ / 1000), this->get_supported_features());
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
const uint16_t flags = alarm_sensor.info.flags;
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Binary Sensor:\n"
|
||||
" Name: %s\n"
|
||||
@@ -67,11 +71,10 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
" Armed night bypass: %s\n"
|
||||
" Auto bypass: %s\n"
|
||||
" Chime mode: %s",
|
||||
sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME));
|
||||
alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_AUTO), TRUEFALSE(flags & BINARY_SENSOR_MODE_CHIME));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -121,7 +124,9 @@ void TemplateAlarmControlPanel::loop() {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// Test all of the sensors regardless of the alarm panel state
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
const auto &info = alarm_sensor.info;
|
||||
auto *sensor = alarm_sensor.sensor;
|
||||
// Check for chime zones
|
||||
if (info.flags & BINARY_SENSOR_MODE_CHIME) {
|
||||
// Look for the transition from closed to open
|
||||
@@ -242,11 +247,11 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
|
||||
|
||||
void TemplateAlarmControlPanel::bypass_before_arming() {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
// Check for faulted bypass_auto sensors and remove them from monitoring
|
||||
if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(info.store_index);
|
||||
if ((alarm_sensor.info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (alarm_sensor.sensor->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", alarm_sensor.sensor->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(alarm_sensor.info.store_index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
|
||||
|
||||
@@ -49,6 +50,13 @@ struct SensorInfo {
|
||||
uint8_t store_index;
|
||||
};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct AlarmSensor {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
SensorInfo info;
|
||||
};
|
||||
#endif
|
||||
|
||||
class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component {
|
||||
public:
|
||||
TemplateAlarmControlPanel();
|
||||
@@ -63,6 +71,12 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
void bypass_before_arming();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
/** Initialize the sensors vector with the specified capacity.
|
||||
*
|
||||
* @param capacity The number of sensors to allocate space for.
|
||||
*/
|
||||
void init_sensors(size_t capacity) { this->sensors_.init(capacity); }
|
||||
|
||||
/** Add a binary_sensor to the alarm_panel.
|
||||
*
|
||||
* @param sensor The BinarySensor instance.
|
||||
@@ -122,8 +136,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
protected:
|
||||
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// This maps a binary sensor to its alarm specific info
|
||||
std::map<binary_sensor::BinarySensor *, SensorInfo> sensor_map_;
|
||||
// List of binary sensors with their alarm-specific info
|
||||
FixedVector<AlarmSensor> sensors_;
|
||||
// a list of automatically bypassed sensors
|
||||
std::vector<uint8_t> bypassed_sensor_indicies_;
|
||||
#endif
|
||||
|
||||
@@ -945,6 +945,10 @@ async def to_code(config):
|
||||
cg.add(var.set_humidity_hysteresis(config[CONF_HUMIDITY_HYSTERESIS]))
|
||||
|
||||
if CONF_PRESET in config:
|
||||
# Separate standard and custom presets, and build preset config variables
|
||||
standard_presets: list[tuple[cg.MockObj, cg.MockObj]] = []
|
||||
custom_presets: list[tuple[str, cg.MockObj]] = []
|
||||
|
||||
for preset_config in config[CONF_PRESET]:
|
||||
name = preset_config[CONF_NAME]
|
||||
standard_preset = None
|
||||
@@ -987,9 +991,39 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
if standard_preset is not None:
|
||||
cg.add(var.set_preset_config(standard_preset, preset_target_variable))
|
||||
standard_presets.append((standard_preset, preset_target_variable))
|
||||
else:
|
||||
cg.add(var.set_custom_preset_config(name, preset_target_variable))
|
||||
custom_presets.append((name, preset_target_variable))
|
||||
|
||||
# Build initializer list for standard presets
|
||||
if standard_presets:
|
||||
cg.add(
|
||||
var.set_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatPresetEntry"),
|
||||
("preset", preset),
|
||||
("config", preset_var),
|
||||
)
|
||||
for preset, preset_var in standard_presets
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# Build initializer list for custom presets
|
||||
if custom_presets:
|
||||
cg.add(
|
||||
var.set_custom_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatCustomPresetEntry"),
|
||||
("name", cg.RawExpression(f'"{name}"')),
|
||||
("config", preset_var),
|
||||
)
|
||||
for name, preset_var in custom_presets
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_DEFAULT_PRESET in config:
|
||||
default_preset_name = config[CONF_DEFAULT_PRESET]
|
||||
|
||||
@@ -53,8 +53,8 @@ void ThermostatClimate::setup() {
|
||||
if (use_default_preset) {
|
||||
if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) {
|
||||
this->change_preset_(this->default_preset_);
|
||||
} else if (!this->default_custom_preset_.empty()) {
|
||||
this->change_custom_preset_(this->default_custom_preset_.c_str());
|
||||
} else if (this->default_custom_preset_ != nullptr) {
|
||||
this->change_custom_preset_(this->default_custom_preset_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,16 +319,16 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
if (this->supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
for (auto &it : this->preset_config_) {
|
||||
traits.add_supported_preset(it.first);
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
traits.add_supported_preset(entry.preset);
|
||||
}
|
||||
|
||||
// Extract custom preset names from the custom_preset_config_ map
|
||||
// Extract custom preset names from the custom_preset_config_ vector
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
std::vector<const char *> custom_preset_names;
|
||||
custom_preset_names.reserve(this->custom_preset_config_.size());
|
||||
for (const auto &it : this->custom_preset_config_) {
|
||||
custom_preset_names.push_back(it.first.c_str());
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
custom_preset_names.push_back(entry.name);
|
||||
}
|
||||
traits.set_supported_custom_presets(custom_preset_names);
|
||||
}
|
||||
@@ -1154,12 +1154,18 @@ void ThermostatClimate::dump_preset_config_(const char *preset_name, const Therm
|
||||
}
|
||||
|
||||
void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
auto config = this->preset_config_.find(preset);
|
||||
// Linear search through preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
if (entry.preset == preset) {
|
||||
config = &entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != this->preset_config_.end()) {
|
||||
if (config != nullptr) {
|
||||
ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||
if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) ||
|
||||
this->preset.value() != preset) {
|
||||
if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
|
||||
// Fire any preset changed trigger if defined
|
||||
Trigger<> *trig = this->preset_change_trigger_;
|
||||
this->set_preset_(preset);
|
||||
@@ -1178,11 +1184,18 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
}
|
||||
|
||||
void ThermostatClimate::change_custom_preset_(const char *custom_preset) {
|
||||
auto config = this->custom_preset_config_.find(custom_preset);
|
||||
// Linear search through custom preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
if (strcmp(entry.name, custom_preset) == 0) {
|
||||
config = &entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != this->custom_preset_config_.end()) {
|
||||
if (config != nullptr) {
|
||||
ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
|
||||
if (this->change_preset_internal_(config->second) || !this->has_custom_preset() ||
|
||||
if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
|
||||
strcmp(this->get_custom_preset(), custom_preset) != 0) {
|
||||
// Fire any preset changed trigger if defined
|
||||
Trigger<> *trig = this->preset_change_trigger_;
|
||||
@@ -1247,14 +1260,12 @@ bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTem
|
||||
return something_changed;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_preset_config(climate::ClimatePreset preset,
|
||||
const ThermostatClimateTargetTempConfig &config) {
|
||||
this->preset_config_[preset] = config;
|
||||
void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> presets) {
|
||||
this->preset_config_ = presets;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_custom_preset_config(const std::string &name,
|
||||
const ThermostatClimateTargetTempConfig &config) {
|
||||
this->custom_preset_config_[name] = config;
|
||||
void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
|
||||
this->custom_preset_config_ = presets;
|
||||
}
|
||||
|
||||
ThermostatClimate::ThermostatClimate()
|
||||
@@ -1293,8 +1304,16 @@ ThermostatClimate::ThermostatClimate()
|
||||
humidity_control_humidify_action_trigger_(new Trigger<>()),
|
||||
humidity_control_off_action_trigger_(new Trigger<>()) {}
|
||||
|
||||
void ThermostatClimate::set_default_preset(const std::string &custom_preset) {
|
||||
this->default_custom_preset_ = custom_preset;
|
||||
void ThermostatClimate::set_default_preset(const char *custom_preset) {
|
||||
// Find the preset in custom_preset_config_ and store pointer from there
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
if (strcmp(entry.name, custom_preset) == 0) {
|
||||
this->default_custom_preset_ = entry.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not found, it will be caught during validation
|
||||
this->default_custom_preset_ = nullptr;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; }
|
||||
@@ -1605,19 +1624,22 @@ void ThermostatClimate::dump_config() {
|
||||
|
||||
if (!this->preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS:");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, entry.config);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
const auto *preset_name = entry.name;
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
|
||||
(this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
|
||||
? " (default)"
|
||||
: "");
|
||||
this->dump_preset_config_(preset_name, entry.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
@@ -72,14 +72,29 @@ struct ThermostatClimateTargetTempConfig {
|
||||
optional<climate::ClimateMode> mode_{};
|
||||
};
|
||||
|
||||
/// Entry for standard preset lookup
|
||||
struct ThermostatPresetEntry {
|
||||
climate::ClimatePreset preset;
|
||||
ThermostatClimateTargetTempConfig config;
|
||||
};
|
||||
|
||||
/// Entry for custom preset lookup
|
||||
struct ThermostatCustomPresetEntry {
|
||||
const char *name;
|
||||
ThermostatClimateTargetTempConfig config;
|
||||
};
|
||||
|
||||
class ThermostatClimate : public climate::Climate, public Component {
|
||||
public:
|
||||
using PresetEntry = ThermostatPresetEntry;
|
||||
using CustomPresetEntry = ThermostatCustomPresetEntry;
|
||||
|
||||
ThermostatClimate();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
void set_default_preset(const std::string &custom_preset);
|
||||
void set_default_preset(const char *custom_preset);
|
||||
void set_default_preset(climate::ClimatePreset preset);
|
||||
void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from);
|
||||
void set_set_point_minimum_differential(float differential);
|
||||
@@ -131,8 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
void set_supports_humidification(bool supports_humidification);
|
||||
void set_supports_two_points(bool supports_two_points);
|
||||
|
||||
void set_preset_config(climate::ClimatePreset preset, const ThermostatClimateTargetTempConfig &config);
|
||||
void set_custom_preset_config(const std::string &name, const ThermostatClimateTargetTempConfig &config);
|
||||
void set_preset_config(std::initializer_list<PresetEntry> presets);
|
||||
void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets);
|
||||
|
||||
Trigger<> *get_cool_action_trigger() const;
|
||||
Trigger<> *get_supplemental_cool_action_trigger() const;
|
||||
@@ -516,9 +531,6 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *prev_swing_mode_trigger_{nullptr};
|
||||
Trigger<> *prev_humidity_control_trigger_{nullptr};
|
||||
|
||||
/// Default custom preset to use on start up
|
||||
std::string default_custom_preset_{};
|
||||
|
||||
/// Climate action timers
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)),
|
||||
@@ -534,9 +546,12 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
|
||||
FixedVector<PresetEntry> preset_config_{};
|
||||
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
|
||||
std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{};
|
||||
FixedVector<CustomPresetEntry> custom_preset_config_{};
|
||||
/// Default custom preset to use on start up (pointer to entry in custom_preset_config_)
|
||||
private:
|
||||
const char *default_custom_preset_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace thermostat
|
||||
|
||||
@@ -23,6 +23,13 @@ namespace time {
|
||||
static const char *const TAG = "time";
|
||||
|
||||
RealTimeClock::RealTimeClock() = default;
|
||||
|
||||
void RealTimeClock::dump_config() {
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
ESP_LOGCONFIG(TAG, "Timezone: '%s'", this->timezone_.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||
// Update UTC epoch time.
|
||||
|
||||
@@ -52,6 +52,8 @@ class RealTimeClock : public PollingComponent {
|
||||
this->time_sync_callback_.add(std::move(callback));
|
||||
};
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
/// Report a unix epoch as current time.
|
||||
void synchronize_epoch_(uint32_t epoch);
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import logging
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_USE_PSRAM
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import (
|
||||
has_high_performance_networking,
|
||||
ip_address_literal,
|
||||
)
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import only_with_esp_idf
|
||||
@@ -42,6 +48,7 @@ from esphome.const import (
|
||||
CONF_TTLS_PHASE_2,
|
||||
CONF_USE_ADDRESS,
|
||||
CONF_USERNAME,
|
||||
Platform,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, HexInt, coroutine_with_priority
|
||||
@@ -49,10 +56,15 @@ import esphome.final_validate as fv
|
||||
|
||||
from . import wpa2_eap
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2, const.VARIANT_ESP32P4]
|
||||
CONF_SAVE = "save"
|
||||
CONF_MIN_AUTH_MODE = "min_auth_mode"
|
||||
|
||||
# Maximum number of WiFi networks that can be configured
|
||||
# Limited to 127 because selected_sta_index_ is int8_t in C++
|
||||
@@ -70,6 +82,14 @@ WIFI_POWER_SAVE_MODES = {
|
||||
"LIGHT": WiFiPowerSaveMode.WIFI_POWER_SAVE_LIGHT,
|
||||
"HIGH": WiFiPowerSaveMode.WIFI_POWER_SAVE_HIGH,
|
||||
}
|
||||
|
||||
WifiMinAuthMode = wifi_ns.enum("WifiMinAuthMode")
|
||||
WIFI_MIN_AUTH_MODES = {
|
||||
"WPA": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA,
|
||||
"WPA2": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA2,
|
||||
"WPA3": WifiMinAuthMode.WIFI_MIN_AUTH_MODE_WPA3,
|
||||
}
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True)
|
||||
WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition)
|
||||
WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition)
|
||||
WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action)
|
||||
@@ -174,7 +194,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
|
||||
{
|
||||
cv.Optional(CONF_BSSID): cv.mac_address,
|
||||
cv.Optional(CONF_HIDDEN): cv.boolean,
|
||||
cv.Optional(CONF_PRIORITY, default=0.0): cv.float_,
|
||||
cv.Optional(CONF_PRIORITY, default=0): cv.int_range(min=-128, max=127),
|
||||
cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA,
|
||||
}
|
||||
)
|
||||
@@ -187,6 +207,27 @@ def validate_variant(_):
|
||||
raise cv.Invalid(f"WiFi requires component esp32_hosted on {variant}")
|
||||
|
||||
|
||||
def _apply_min_auth_mode_default(config):
|
||||
"""Apply platform-specific default for min_auth_mode and warn ESP8266 users."""
|
||||
# Only apply defaults for platforms that support min_auth_mode
|
||||
if CONF_MIN_AUTH_MODE not in config and (CORE.is_esp8266 or CORE.is_esp32):
|
||||
if CORE.is_esp8266:
|
||||
_LOGGER.warning(
|
||||
"The minimum WiFi authentication mode (wifi -> min_auth_mode) is not set. "
|
||||
"This controls the weakest encryption your device will accept when connecting to WiFi. "
|
||||
"Currently defaults to WPA (less secure), but will change to WPA2 (more secure) in 2026.6.0. "
|
||||
"WPA uses TKIP encryption which has known security vulnerabilities and should be avoided. "
|
||||
"WPA2 uses AES encryption which is significantly more secure. "
|
||||
"To silence this warning, explicitly set min_auth_mode under 'wifi:'. "
|
||||
"If your router supports WPA2 or WPA3, set 'min_auth_mode: WPA2'. "
|
||||
"If your router only supports WPA, set 'min_auth_mode: WPA'."
|
||||
)
|
||||
config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA")
|
||||
elif CORE.is_esp32:
|
||||
config[CONF_MIN_AUTH_MODE] = VALIDATE_WIFI_MIN_AUTH_MODE("WPA2")
|
||||
return config
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
has_sta = bool(config.get(CONF_NETWORKS, True))
|
||||
has_ap = CONF_AP in config
|
||||
@@ -287,6 +328,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
|
||||
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_MIN_AUTH_MODE): cv.All(
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE,
|
||||
cv.only_on([Platform.ESP32, Platform.ESP8266]),
|
||||
),
|
||||
cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All(
|
||||
cv.decibel, cv.float_range(min=8.5, max=20.5)
|
||||
),
|
||||
@@ -311,6 +356,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
),
|
||||
_apply_min_auth_mode_default,
|
||||
_validate,
|
||||
)
|
||||
|
||||
@@ -385,6 +431,8 @@ async def to_code(config):
|
||||
|
||||
# Track if any network uses Enterprise authentication
|
||||
has_eap = False
|
||||
# Track if any network uses manual IP
|
||||
has_manual_ip = False
|
||||
|
||||
# Initialize FixedVector with the count of networks
|
||||
networks = config.get(CONF_NETWORKS, [])
|
||||
@@ -398,11 +446,15 @@ async def to_code(config):
|
||||
for network in networks:
|
||||
if CONF_EAP in network:
|
||||
has_eap = True
|
||||
if network.get(CONF_MANUAL_IP) or config.get(CONF_MANUAL_IP):
|
||||
has_manual_ip = True
|
||||
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
|
||||
|
||||
if CONF_AP in config:
|
||||
conf = config[CONF_AP]
|
||||
ip_config = conf.get(CONF_MANUAL_IP)
|
||||
if ip_config:
|
||||
has_manual_ip = True
|
||||
cg.with_local_variable(
|
||||
conf[CONF_ID],
|
||||
WiFiAP(),
|
||||
@@ -418,8 +470,14 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap)
|
||||
|
||||
# Only define USE_WIFI_MANUAL_IP if any AP uses manual IP
|
||||
if has_manual_ip:
|
||||
cg.add_define("USE_WIFI_MANUAL_IP")
|
||||
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
|
||||
if CONF_MIN_AUTH_MODE in config:
|
||||
cg.add(var.set_min_auth_mode(config[CONF_MIN_AUTH_MODE]))
|
||||
if config[CONF_FAST_CONNECT]:
|
||||
cg.add_define("USE_WIFI_FAST_CONNECT")
|
||||
cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN]))
|
||||
@@ -444,6 +502,56 @@ async def to_code(config):
|
||||
|
||||
if config.get(CONF_USE_PSRAM):
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
# Apply high performance WiFi settings if high performance networking is enabled
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and has_high_performance_networking():
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
# Always allocate WiFi buffers in PSRAM if available
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
if psram_guaranteed:
|
||||
_LOGGER.info(
|
||||
"Applying high-performance WiFi settings (PSRAM guaranteed): 512 RX buffers, 32 TX buffers"
|
||||
)
|
||||
# PSRAM is guaranteed - use aggressive settings
|
||||
# Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true in networking component
|
||||
# Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
|
||||
|
||||
# Large dynamic RX buffers (requires PSRAM)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 512)
|
||||
|
||||
# Static TX buffers for better performance
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BUFFER_TYPE", 0)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM", 32)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM", 8)
|
||||
|
||||
# AMPDU settings optimized for PSRAM
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Applying optimized WiFi settings: 64 RX buffers, 64 TX buffers"
|
||||
)
|
||||
# PSRAM not guaranteed - use more conservative, but still optimized settings
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
|
||||
# Standard buffer counts
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64)
|
||||
|
||||
# Standard AMPDU settings
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
|
||||
cg.add_define("USE_WIFI")
|
||||
|
||||
# must register before OTA safe mode check
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,9 @@ extern "C" {
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
|
||||
/// Sentinel value for RSSI when WiFi is not connected
|
||||
static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127;
|
||||
|
||||
struct SavedWifiSettings {
|
||||
char ssid[33];
|
||||
char password[65];
|
||||
@@ -74,12 +77,6 @@ enum WiFiComponentState : uint8_t {
|
||||
WIFI_COMPONENT_STATE_STA_SCANNING,
|
||||
/** WiFi is in STA(+AP) mode and currently connecting to an AP. */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTING,
|
||||
/** WiFi is in STA(+AP) mode and currently connecting to an AP a second time.
|
||||
*
|
||||
* This is required because for some reason ESPs don't like to connect to WiFi APs directly after
|
||||
* a scan.
|
||||
* */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTING_2,
|
||||
/** WiFi is in STA(+AP) mode and successfully connected. */
|
||||
WIFI_COMPONENT_STATE_STA_CONNECTED,
|
||||
/** WiFi is in AP-only mode and internal AP is already enabled. */
|
||||
@@ -94,6 +91,24 @@ enum class WiFiSTAConnectStatus : int {
|
||||
ERROR_CONNECT_FAILED,
|
||||
};
|
||||
|
||||
/// Tracks the current retry strategy/phase for WiFi connection attempts
|
||||
enum class WiFiRetryPhase : uint8_t {
|
||||
/// Initial connection attempt (varies based on fast_connect setting)
|
||||
INITIAL_CONNECT,
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
/// Fast connect mode: cycling through configured APs (config-only, no scan)
|
||||
FAST_CONNECT_CYCLING_APS,
|
||||
#endif
|
||||
/// Explicitly hidden networks (user marked as hidden, try before scanning)
|
||||
EXPLICIT_HIDDEN,
|
||||
/// Scan-based: connecting to best AP from scan results
|
||||
SCAN_CONNECTING,
|
||||
/// Retry networks not found in scan (might be hidden)
|
||||
RETRY_HIDDEN,
|
||||
/// Restarting WiFi adapter to clear stuck state
|
||||
RESTARTING_ADAPTER,
|
||||
};
|
||||
|
||||
/// Struct for setting static IPs in WiFiComponent.
|
||||
struct ManualIP {
|
||||
network::IPAddress static_ip;
|
||||
@@ -139,8 +154,10 @@ class WiFiAP {
|
||||
void set_eap(optional<EAPAuth> eap_auth);
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
void set_channel(optional<uint8_t> channel);
|
||||
void set_priority(float priority) { priority_ = priority; }
|
||||
void set_priority(int8_t priority) { priority_ = priority; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
void set_manual_ip(optional<ManualIP> manual_ip);
|
||||
#endif
|
||||
void set_hidden(bool hidden);
|
||||
const std::string &get_ssid() const;
|
||||
const optional<bssid_t> &get_bssid() const;
|
||||
@@ -149,8 +166,10 @@ class WiFiAP {
|
||||
const optional<EAPAuth> &get_eap() const;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
const optional<uint8_t> &get_channel() const;
|
||||
float get_priority() const { return priority_; }
|
||||
int8_t get_priority() const { return priority_; }
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
const optional<ManualIP> &get_manual_ip() const;
|
||||
#endif
|
||||
bool get_hidden() const;
|
||||
|
||||
protected:
|
||||
@@ -160,9 +179,11 @@ class WiFiAP {
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
optional<EAPAuth> eap_;
|
||||
#endif // USE_WIFI_WPA2_EAP
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_;
|
||||
float priority_{0};
|
||||
#endif
|
||||
optional<uint8_t> channel_;
|
||||
int8_t priority_{0};
|
||||
bool hidden_{false};
|
||||
};
|
||||
|
||||
@@ -180,17 +201,17 @@ class WiFiScanResult {
|
||||
int8_t get_rssi() const;
|
||||
bool get_with_auth() const;
|
||||
bool get_is_hidden() const;
|
||||
float get_priority() const { return priority_; }
|
||||
void set_priority(float priority) { priority_ = priority; }
|
||||
int8_t get_priority() const { return priority_; }
|
||||
void set_priority(int8_t priority) { priority_ = priority; }
|
||||
|
||||
bool operator==(const WiFiScanResult &rhs) const;
|
||||
|
||||
protected:
|
||||
bssid_t bssid_;
|
||||
std::string ssid_;
|
||||
float priority_{0.0f};
|
||||
uint8_t channel_;
|
||||
int8_t rssi_;
|
||||
std::string ssid_;
|
||||
int8_t priority_{0};
|
||||
bool matches_{false};
|
||||
bool with_auth_;
|
||||
bool is_hidden_;
|
||||
@@ -198,7 +219,7 @@ class WiFiScanResult {
|
||||
|
||||
struct WiFiSTAPriority {
|
||||
bssid_t bssid;
|
||||
float priority;
|
||||
int8_t priority;
|
||||
};
|
||||
|
||||
enum WiFiPowerSaveMode : uint8_t {
|
||||
@@ -207,6 +228,12 @@ enum WiFiPowerSaveMode : uint8_t {
|
||||
WIFI_POWER_SAVE_HIGH,
|
||||
};
|
||||
|
||||
enum WifiMinAuthMode : uint8_t {
|
||||
WIFI_MIN_AUTH_MODE_WPA = 0,
|
||||
WIFI_MIN_AUTH_MODE_WPA2,
|
||||
WIFI_MIN_AUTH_MODE_WPA3,
|
||||
};
|
||||
|
||||
#ifdef USE_ESP32
|
||||
struct IDFWiFiEvent;
|
||||
#endif
|
||||
@@ -245,19 +272,20 @@ class WiFiComponent : public Component {
|
||||
bool is_disabled();
|
||||
void start_scanning();
|
||||
void check_scanning_finished();
|
||||
void start_connecting(const WiFiAP &ap, bool two);
|
||||
void start_connecting(const WiFiAP &ap);
|
||||
// Backward compatibility overload - ignores 'two' parameter
|
||||
void start_connecting(const WiFiAP &ap, bool /* two */) { this->start_connecting(ap); }
|
||||
|
||||
void check_connecting_finished();
|
||||
|
||||
void retry_connect();
|
||||
|
||||
bool can_proceed() override;
|
||||
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
bool is_connected();
|
||||
|
||||
void set_power_save_mode(WiFiPowerSaveMode power_save);
|
||||
void set_min_auth_mode(WifiMinAuthMode min_auth_mode) { min_auth_mode_ = min_auth_mode; }
|
||||
void set_output_power(float output_power) { output_power_ = output_power; }
|
||||
|
||||
void set_passive_scan(bool passive);
|
||||
@@ -301,14 +329,14 @@ class WiFiComponent : public Component {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
float get_sta_priority(const bssid_t bssid) {
|
||||
int8_t get_sta_priority(const bssid_t bssid) {
|
||||
for (auto &it : this->sta_priorities_) {
|
||||
if (it.bssid == bssid)
|
||||
return it.priority;
|
||||
}
|
||||
return 0.0f;
|
||||
return 0;
|
||||
}
|
||||
void set_sta_priority(const bssid_t bssid, float priority) {
|
||||
void set_sta_priority(const bssid_t bssid, int8_t priority) {
|
||||
for (auto &it : this->sta_priorities_) {
|
||||
if (it.bssid == bssid) {
|
||||
it.priority = priority;
|
||||
@@ -341,8 +369,38 @@ class WiFiComponent : public Component {
|
||||
#endif // USE_WIFI_AP
|
||||
|
||||
void print_connect_params_();
|
||||
WiFiAP build_wifi_ap_from_selected_() const;
|
||||
WiFiAP build_params_for_current_phase_();
|
||||
|
||||
/// Determine next retry phase based on current state and failure conditions
|
||||
WiFiRetryPhase determine_next_phase_();
|
||||
/// Transition to a new retry phase with logging
|
||||
/// Returns true if a scan was started (caller should wait), false otherwise
|
||||
bool transition_to_phase_(WiFiRetryPhase new_phase);
|
||||
/// Check if we need valid scan results for the current phase but don't have any
|
||||
/// Returns true if the phase requires scan results but they're missing or don't match
|
||||
bool needs_scan_results_() const;
|
||||
/// Check if we went through EXPLICIT_HIDDEN phase (first network is marked hidden)
|
||||
/// Used in RETRY_HIDDEN to determine whether to skip explicitly hidden networks
|
||||
bool went_through_explicit_hidden_phase_() const;
|
||||
/// Find the index of the first non-hidden network
|
||||
/// Returns where EXPLICIT_HIDDEN phase would have stopped, or -1 if all networks are hidden
|
||||
int8_t find_first_non_hidden_index_() const;
|
||||
/// Check if an SSID was seen in the most recent scan results
|
||||
/// Used to skip hidden mode for SSIDs we know are visible
|
||||
bool ssid_was_seen_in_scan_(const std::string &ssid) const;
|
||||
/// Find next SSID that wasn't in scan results (might be hidden)
|
||||
/// Returns index of next potentially hidden SSID, or -1 if none found
|
||||
/// @param start_index Start searching from index after this (-1 to start from beginning)
|
||||
int8_t find_next_hidden_sta_(int8_t start_index);
|
||||
/// Log failed connection and decrease BSSID priority to avoid repeated attempts
|
||||
void log_and_adjust_priority_for_failed_connect_();
|
||||
/// Clear BSSID priority tracking if all priorities are at minimum (saves memory)
|
||||
void clear_priorities_if_all_min_();
|
||||
/// Advance to next target (AP/SSID) within current phase, or increment retry counter
|
||||
/// Called when staying in the same phase after a failed connection attempt
|
||||
void advance_to_next_target_or_increment_retry_();
|
||||
/// Start initial connection - either scan or connect directly to hidden networks
|
||||
void start_initial_connection_();
|
||||
const WiFiAP *get_selected_sta_() const {
|
||||
if (this->selected_sta_index_ >= 0 && static_cast<size_t>(this->selected_sta_index_) < this->sta_.size()) {
|
||||
return &this->sta_[this->selected_sta_index_];
|
||||
@@ -356,21 +414,22 @@ class WiFiComponent : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
// Reset state for next fast connect AP attempt
|
||||
// Clears old scan data so the new AP is tried with config only (SSID without specific BSSID/channel)
|
||||
void reset_for_next_ap_attempt_() {
|
||||
this->num_retried_ = 0;
|
||||
this->scan_result_.clear();
|
||||
bool all_networks_hidden_() const {
|
||||
if (this->sta_.empty())
|
||||
return false;
|
||||
for (const auto &ap : this->sta_) {
|
||||
if (!ap.get_hidden())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void wifi_loop_();
|
||||
bool wifi_mode_(optional<bool> sta, optional<bool> ap);
|
||||
bool wifi_sta_pre_setup_();
|
||||
bool wifi_apply_output_power_(float output_power);
|
||||
bool wifi_apply_power_save_();
|
||||
bool wifi_sta_ip_config_(optional<ManualIP> manual_ip);
|
||||
bool wifi_sta_ip_config_(const optional<ManualIP> &manual_ip);
|
||||
bool wifi_apply_hostname_();
|
||||
bool wifi_sta_connect_(const WiFiAP &ap);
|
||||
void wifi_pre_setup_();
|
||||
@@ -378,7 +437,7 @@ class WiFiComponent : public Component {
|
||||
bool wifi_scan_start_(bool passive);
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
bool wifi_ap_ip_config_(optional<ManualIP> manual_ip);
|
||||
bool wifi_ap_ip_config_(const optional<ManualIP> &manual_ip);
|
||||
bool wifi_start_ap_(const WiFiAP &ap);
|
||||
#endif // USE_WIFI_AP
|
||||
|
||||
@@ -443,20 +502,19 @@ class WiFiComponent : public Component {
|
||||
// Group all 8-bit values together
|
||||
WiFiComponentState state_{WIFI_COMPONENT_STATE_OFF};
|
||||
WiFiPowerSaveMode power_save_{WIFI_POWER_SAVE_NONE};
|
||||
WifiMinAuthMode min_auth_mode_{WIFI_MIN_AUTH_MODE_WPA2};
|
||||
WiFiRetryPhase retry_phase_{WiFiRetryPhase::INITIAL_CONNECT};
|
||||
uint8_t num_retried_{0};
|
||||
// Index into sta_ array for the currently selected AP configuration (-1 = none selected)
|
||||
// Used to access password, manual_ip, priority, EAP settings, and hidden flag
|
||||
// int8_t limits to 127 APs (enforced in __init__.py via MAX_WIFI_NETWORKS)
|
||||
int8_t selected_sta_index_{-1};
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
uint8_t num_ipv6_addresses_{0};
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
// Group all boolean values together
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
bool trying_loaded_ap_{false};
|
||||
#endif
|
||||
bool retry_hidden_{false};
|
||||
bool has_ap_{false};
|
||||
bool handled_connected_state_{false};
|
||||
bool error_from_callback_{false};
|
||||
|
||||
@@ -117,7 +117,7 @@ void netif_set_addr(struct netif *netif, const ip4_addr_t *ip, const ip4_addr_t
|
||||
};
|
||||
#endif
|
||||
|
||||
bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_sta_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
@@ -258,8 +258,17 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
if (ap.get_password().empty()) {
|
||||
conf.threshold.authmode = AUTH_OPEN;
|
||||
} else {
|
||||
// Only allow auth modes with at least WPA
|
||||
conf.threshold.authmode = AUTH_WPA_PSK;
|
||||
// Set threshold based on configured minimum auth mode
|
||||
// Note: ESP8266 doesn't support WPA3
|
||||
switch (this->min_auth_mode_) {
|
||||
case WIFI_MIN_AUTH_MODE_WPA:
|
||||
conf.threshold.authmode = AUTH_WPA_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA2:
|
||||
case WIFI_MIN_AUTH_MODE_WPA3: // Fall back to WPA2 for ESP8266
|
||||
conf.threshold.authmode = AUTH_WPA2_PSK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
conf.threshold.rssi = -127;
|
||||
#endif
|
||||
@@ -273,9 +282,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup enterprise authentication if required
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -510,8 +525,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||
s_sta_connect_not_found = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
||||
char bssid_s[18];
|
||||
format_mac_addr_upper(it.bssid, bssid_s);
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
|
||||
LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
||||
s_sta_connect_error = true;
|
||||
}
|
||||
s_sta_connected = false;
|
||||
@@ -715,7 +732,7 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
// enable AP
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
@@ -823,10 +840,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -848,7 +872,7 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
|
||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
|
||||
|
||||
@@ -308,7 +308,18 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
if (ap.get_password().empty()) {
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
|
||||
} else {
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK;
|
||||
// Set threshold based on configured minimum auth mode
|
||||
switch (this->min_auth_mode_) {
|
||||
case WIFI_MIN_AUTH_MODE_WPA:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA2:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
break;
|
||||
case WIFI_MIN_AUTH_MODE_WPA3:
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_WPA3_PSK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -347,8 +358,6 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
// The minimum rssi to accept in the fast scan mode
|
||||
conf.sta.threshold.rssi = -127;
|
||||
|
||||
conf.sta.threshold.authmode = WIFI_AUTH_OPEN;
|
||||
|
||||
wifi_config_t current_conf;
|
||||
esp_err_t err;
|
||||
err = esp_wifi_get_config(WIFI_IF_STA, ¤t_conf);
|
||||
@@ -371,9 +380,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// setup enterprise authentication if required
|
||||
#ifdef USE_WIFI_WPA2_EAP
|
||||
@@ -472,7 +487,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_sta_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
@@ -731,8 +746,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf);
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
char bssid_s[18];
|
||||
format_mac_addr_upper(it.bssid, bssid_s);
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
|
||||
get_disconnect_reason_str(it.reason));
|
||||
s_sta_connect_error = true;
|
||||
}
|
||||
s_sta_connected = false;
|
||||
@@ -869,7 +886,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
esp_err_t err;
|
||||
|
||||
// enable AP
|
||||
@@ -985,10 +1002,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGE(TAG, "wifi_ap_ip_config_ failed:");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1007,7 +1031,8 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
wifi_ap_record_t info;
|
||||
esp_err_t err = esp_wifi_sta_get_ap_info(&info);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
// Very verbose only: this is expected during dump_config() before connection is established (PR #9823)
|
||||
ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
return bssid;
|
||||
}
|
||||
std::copy(info.bssid, info.bssid + 6, bssid.begin());
|
||||
@@ -1017,7 +1042,8 @@ std::string WiFiComponent::wifi_ssid() {
|
||||
wifi_ap_record_t info{};
|
||||
esp_err_t err = esp_wifi_sta_get_ap_info(&info);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
// Very verbose only: this is expected during dump_config() before connection is established (PR #9823)
|
||||
ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
return "";
|
||||
}
|
||||
auto *ssid_s = reinterpret_cast<const char *>(info.ssid);
|
||||
@@ -1028,8 +1054,9 @@ int8_t WiFiComponent::wifi_rssi() {
|
||||
wifi_ap_record_t info;
|
||||
esp_err_t err = esp_wifi_sta_get_ap_info(&info);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
return 0;
|
||||
// Very verbose only: this is expected during dump_config() before connection is established (PR #9823)
|
||||
ESP_LOGVV(TAG, "esp_wifi_sta_get_ap_info failed: %s", esp_err_to_name(err));
|
||||
return WIFI_RSSI_DISCONNECTED;
|
||||
}
|
||||
return info.rssi;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ bool WiFiComponent::wifi_sta_pre_setup_() {
|
||||
return true;
|
||||
}
|
||||
bool WiFiComponent::wifi_apply_power_save_() { return WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE); }
|
||||
bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_sta_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
// enable STA
|
||||
if (!this->wifi_mode_(true, {}))
|
||||
return false;
|
||||
@@ -112,9 +112,15 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
WiFi.disconnect();
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({})) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->wifi_apply_hostname_();
|
||||
|
||||
@@ -293,8 +299,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
char bssid_s[18];
|
||||
format_mac_addr_upper(it.bssid, bssid_s);
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
|
||||
get_disconnect_reason_str(it.reason));
|
||||
}
|
||||
|
||||
uint8_t reason = it.reason;
|
||||
@@ -428,7 +436,7 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
// enable AP
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
@@ -445,10 +453,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
yield();
|
||||
|
||||
@@ -471,7 +486,7 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; }
|
||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
|
||||
|
||||
@@ -55,8 +55,13 @@ bool WiFiComponent::wifi_apply_power_save_() {
|
||||
bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; }
|
||||
|
||||
bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_sta_ip_config_(ap.get_manual_ip()))
|
||||
return false;
|
||||
#else
|
||||
if (!this->wifi_sta_ip_config_({}))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str());
|
||||
if (ret != WL_CONNECTED)
|
||||
@@ -67,7 +72,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
|
||||
bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); }
|
||||
|
||||
bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_sta_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
if (!manual_ip.has_value()) {
|
||||
return true;
|
||||
}
|
||||
@@ -141,7 +146,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
|
||||
esphome::network::IPAddress ip_address, gateway, subnet, dns;
|
||||
if (manual_ip.has_value()) {
|
||||
ip_address = manual_ip->static_ip;
|
||||
@@ -161,10 +166,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
|
||||
bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
|
||||
if (!this->wifi_mode_({}, true))
|
||||
return false;
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (!this->wifi_ap_ip_config_(ap.get_manual_ip())) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!this->wifi_ap_ip_config_({})) {
|
||||
ESP_LOGV(TAG, "wifi_ap_ip_config_ failed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
WiFi.beginAP(ap.get_ssid().c_str(), ap.get_password().c_str(), ap.get_channel().value_or(1));
|
||||
|
||||
@@ -188,7 +200,7 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
return bssid;
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
|
||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||
|
||||
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.11.0-dev"
|
||||
__version__ = "2025.12.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -412,7 +412,12 @@ template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Co
|
||||
|
||||
void setup() override {
|
||||
// Start with loop disabled - only enable when there's work to do
|
||||
this->disable_loop();
|
||||
// IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
|
||||
// called before our setup() (e.g., from on_boot trigger at same priority level)
|
||||
// and we must not undo its enable_loop() call
|
||||
if (this->num_running_ == 0) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void play_complex(const Ts &...x) override {
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
#define USE_TIME_TIMEZONE
|
||||
#define USE_WIFI
|
||||
#define USE_WIFI_AP
|
||||
#define USE_WIFI_MANUAL_IP
|
||||
#define USE_WIREGUARD
|
||||
#endif
|
||||
|
||||
@@ -287,6 +288,8 @@
|
||||
|
||||
#ifdef USE_NRF52
|
||||
#define USE_NRF52_DFU
|
||||
#define USE_NRF52_REG0_VOUT 5
|
||||
#define USE_NRF52_UICR_ERASE
|
||||
#define USE_SOFTDEVICE_ID 7
|
||||
#define USE_SOFTDEVICE_VERSION 1
|
||||
#endif
|
||||
|
||||
@@ -1174,12 +1174,18 @@ template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
||||
* Functions to constrain the range of arithmetic values.
|
||||
*/
|
||||
|
||||
template<std::totally_ordered T> T clamp_at_least(T value, T min) {
|
||||
template<typename T, typename U>
|
||||
concept comparable_with = requires(T a, U b) {
|
||||
{ a > b } -> std::convertible_to<bool>;
|
||||
{ a < b } -> std::convertible_to<bool>;
|
||||
};
|
||||
|
||||
template<std::totally_ordered T, comparable_with<T> U> T clamp_at_least(T value, U min) {
|
||||
if (value < min)
|
||||
return min;
|
||||
return value;
|
||||
}
|
||||
template<std::totally_ordered T> T clamp_at_most(T value, T max) {
|
||||
template<std::totally_ordered T, comparable_with<T> U> T clamp_at_most(T value, U max) {
|
||||
if (value > max)
|
||||
return max;
|
||||
return value;
|
||||
|
||||
@@ -6,3 +6,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,7 @@ from esphome.const import (
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.helpers import get_int_env, get_str_env
|
||||
from esphome.log import AnsiFore, color
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -154,8 +155,12 @@ def show_discover(config, username=None, password=None, client_id=None):
|
||||
|
||||
|
||||
def get_esphome_device_ip(
|
||||
config, username=None, password=None, client_id=None, timeout=25
|
||||
):
|
||||
config: ConfigType,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
client_id: str | None = None,
|
||||
timeout: int | float = 25,
|
||||
) -> list[str]:
|
||||
if CONF_MQTT not in config:
|
||||
raise EsphomeError(
|
||||
"Cannot discover IP via MQTT as the config does not include the mqtt: "
|
||||
@@ -166,6 +171,10 @@ def get_esphome_device_ip(
|
||||
"Cannot discover IP via MQTT as the config does not include the device name: "
|
||||
"component"
|
||||
)
|
||||
if not config[CONF_MQTT].get(CONF_BROKER):
|
||||
raise EsphomeError(
|
||||
"Cannot discover IP via MQTT as the broker is not configured"
|
||||
)
|
||||
|
||||
dev_name = config[CONF_ESPHOME][CONF_NAME]
|
||||
dev_ip = None
|
||||
|
||||
@@ -5,7 +5,7 @@ pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
pytest==9.0.0
|
||||
pytest==9.0.1
|
||||
pytest-cov==7.0.0
|
||||
pytest-mock==3.15.1
|
||||
pytest-asyncio==1.3.0
|
||||
|
||||
@@ -86,6 +86,7 @@ ISOLATED_COMPONENTS = {
|
||||
"modbus_controller": "Defines multiple modbus buses for testing client/server functionality - conflicts with package modbus bus",
|
||||
"neopixelbus": "RMT type conflict with ESP32 Arduino/ESP-IDF headers (enum vs struct rmt_channel_t)",
|
||||
"packages": "cannot merge packages",
|
||||
"tinyusb": "Conflicts with usb_host component - cannot be used together",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -16,5 +16,6 @@ display:
|
||||
|
||||
touchscreen:
|
||||
- platform: chsc6x
|
||||
i2c_id: i2c_bus
|
||||
display: ili9xxx_display
|
||||
interrupt_pin: 22
|
||||
|
||||
4
tests/components/ds1307/test.nrf52-adafruit.yaml
Normal file
4
tests/components/ds1307/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
27
tests/components/esp32/test.esp32-p4-idf.yaml
Normal file
27
tests/components/esp32/test.esp32-p4-idf.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
esp32:
|
||||
variant: esp32p4
|
||||
flash_size: 32MB
|
||||
cpu_frequency: 400MHz
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
enable_idf_experimental_features: yes
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
esp32_hosted:
|
||||
variant: ESP32C6
|
||||
slot: 1
|
||||
active_high: true
|
||||
reset_pin: GPIO15
|
||||
cmd_pin: GPIO13
|
||||
clk_pin: GPIO12
|
||||
d0_pin: GPIO11
|
||||
d1_pin: GPIO10
|
||||
d2_pin: GPIO9
|
||||
d3_pin: GPIO8
|
||||
41
tests/components/hlk_fm22x/common.yaml
Normal file
41
tests/components/hlk_fm22x/common.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hlk_fm22x.enroll:
|
||||
name: "Test"
|
||||
direction: 1
|
||||
- hlk_fm22x.delete_all:
|
||||
|
||||
hlk_fm22x:
|
||||
on_face_scan_matched:
|
||||
- logger.log: test_hlk_22x_face_scan_matched
|
||||
on_face_scan_unmatched:
|
||||
- logger.log: test_hlk_22x_face_scan_unmatched
|
||||
on_face_scan_invalid:
|
||||
- logger.log: test_hlk_22x_face_scan_invalid
|
||||
on_face_info:
|
||||
- logger.log: test_hlk_22x_face_info
|
||||
on_enrollment_done:
|
||||
- logger.log: test_hlk_22x_enrollment_done
|
||||
on_enrollment_failed:
|
||||
- logger.log: test_hlk_22x_enrollment_failed
|
||||
|
||||
sensor:
|
||||
- platform: hlk_fm22x
|
||||
face_count:
|
||||
name: "Face Count"
|
||||
last_face_id:
|
||||
name: "Last Face ID"
|
||||
status:
|
||||
name: "Face Status"
|
||||
|
||||
binary_sensor:
|
||||
- platform: hlk_fm22x
|
||||
name: "Face Enrolling"
|
||||
|
||||
text_sensor:
|
||||
- platform: hlk_fm22x
|
||||
version:
|
||||
name: "HLK Version"
|
||||
last_face_name:
|
||||
name: "Last Face Name"
|
||||
@@ -1,47 +1,4 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hlk_fm22x.enroll:
|
||||
name: "Test"
|
||||
direction: 1
|
||||
- hlk_fm22x.delete_all:
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
uart:
|
||||
- id: uart_hlk_fm22x
|
||||
tx_pin: 17
|
||||
rx_pin: 16
|
||||
baud_rate: 115200
|
||||
|
||||
hlk_fm22x:
|
||||
on_face_scan_matched:
|
||||
- logger.log: test_hlk_22x_face_scan_matched
|
||||
on_face_scan_unmatched:
|
||||
- logger.log: test_hlk_22x_face_scan_unmatched
|
||||
on_face_scan_invalid:
|
||||
- logger.log: test_hlk_22x_face_scan_invalid
|
||||
on_face_info:
|
||||
- logger.log: test_hlk_22x_face_info
|
||||
on_enrollment_done:
|
||||
- logger.log: test_hlk_22x_enrollment_done
|
||||
on_enrollment_failed:
|
||||
- logger.log: test_hlk_22x_enrollment_failed
|
||||
|
||||
sensor:
|
||||
- platform: hlk_fm22x
|
||||
face_count:
|
||||
name: "Face Count"
|
||||
last_face_id:
|
||||
name: "Last Face ID"
|
||||
status:
|
||||
name: "Face Status"
|
||||
|
||||
binary_sensor:
|
||||
- platform: hlk_fm22x
|
||||
name: "Face Enrolling"
|
||||
|
||||
text_sensor:
|
||||
- platform: hlk_fm22x
|
||||
version:
|
||||
name: "HLK Version"
|
||||
last_face_name:
|
||||
name: "Last Face Name"
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,47 +1,4 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hlk_fm22x.enroll:
|
||||
name: "Test"
|
||||
direction: 1
|
||||
- hlk_fm22x.delete_all:
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
uart:
|
||||
- id: uart_hlk_fm22x
|
||||
tx_pin: 4
|
||||
rx_pin: 5
|
||||
baud_rate: 115200
|
||||
|
||||
hlk_fm22x:
|
||||
on_face_scan_matched:
|
||||
- logger.log: test_hlk_22x_face_scan_matched
|
||||
on_face_scan_unmatched:
|
||||
- logger.log: test_hlk_22x_face_scan_unmatched
|
||||
on_face_scan_invalid:
|
||||
- logger.log: test_hlk_22x_face_scan_invalid
|
||||
on_face_info:
|
||||
- logger.log: test_hlk_22x_face_info
|
||||
on_enrollment_done:
|
||||
- logger.log: test_hlk_22x_enrollment_done
|
||||
on_enrollment_failed:
|
||||
- logger.log: test_hlk_22x_enrollment_failed
|
||||
|
||||
sensor:
|
||||
- platform: hlk_fm22x
|
||||
face_count:
|
||||
name: "Face Count"
|
||||
last_face_id:
|
||||
name: "Last Face ID"
|
||||
status:
|
||||
name: "Face Status"
|
||||
|
||||
binary_sensor:
|
||||
- platform: hlk_fm22x
|
||||
name: "Face Enrolling"
|
||||
|
||||
text_sensor:
|
||||
- platform: hlk_fm22x
|
||||
version:
|
||||
name: "HLK Version"
|
||||
last_face_name:
|
||||
name: "Last Face Name"
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,47 +1,4 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hlk_fm22x.enroll:
|
||||
name: "Test"
|
||||
direction: 1
|
||||
- hlk_fm22x.delete_all:
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
uart:
|
||||
- id: uart_hlk_fm22x
|
||||
tx_pin: 4
|
||||
rx_pin: 5
|
||||
baud_rate: 115200
|
||||
|
||||
hlk_fm22x:
|
||||
on_face_scan_matched:
|
||||
- logger.log: test_hlk_22x_face_scan_matched
|
||||
on_face_scan_unmatched:
|
||||
- logger.log: test_hlk_22x_face_scan_unmatched
|
||||
on_face_scan_invalid:
|
||||
- logger.log: test_hlk_22x_face_scan_invalid
|
||||
on_face_info:
|
||||
- logger.log: test_hlk_22x_face_info
|
||||
on_enrollment_done:
|
||||
- logger.log: test_hlk_22x_enrollment_done
|
||||
on_enrollment_failed:
|
||||
- logger.log: test_hlk_22x_enrollment_failed
|
||||
|
||||
sensor:
|
||||
- platform: hlk_fm22x
|
||||
face_count:
|
||||
name: "Face Count"
|
||||
last_face_id:
|
||||
name: "Last Face ID"
|
||||
status:
|
||||
name: "Face Status"
|
||||
|
||||
binary_sensor:
|
||||
- platform: hlk_fm22x
|
||||
name: "Face Enrolling"
|
||||
|
||||
text_sensor:
|
||||
- platform: hlk_fm22x
|
||||
version:
|
||||
name: "HLK Version"
|
||||
last_face_name:
|
||||
name: "Last Face Name"
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -700,6 +700,10 @@ lvgl:
|
||||
width: 100%
|
||||
height: 10%
|
||||
align: top_mid
|
||||
on_value:
|
||||
- lvgl.spinbox.update:
|
||||
id: spinbox_id
|
||||
value: !lambda return x;
|
||||
- button:
|
||||
styles: spin_button
|
||||
id: spin_up
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
matrix_keypad:
|
||||
id: keypad
|
||||
rows:
|
||||
- pin: 10
|
||||
- pin: 11
|
||||
columns:
|
||||
- pin: 12
|
||||
- pin: 13
|
||||
keys: "1234"
|
||||
has_pulldowns: true
|
||||
on_key:
|
||||
- lambda: ESP_LOGI("KEY", "key %d pressed", x);
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1 +1,4 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
network:
|
||||
enable_high_performance: true
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -15,3 +15,7 @@ nrf52:
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
dcdc: False
|
||||
reg0:
|
||||
voltage: 2.1V
|
||||
uicr_erase: true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
nrf52:
|
||||
reg0:
|
||||
voltage: 3.3V
|
||||
uicr_erase: true
|
||||
|
||||
@@ -5,3 +5,5 @@ nrf52:
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
reg0:
|
||||
voltage: 1.8V
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
substitutions:
|
||||
pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/pcf85063/test.nrf52-adafruit.yaml
Normal file
4
tests/components/pcf85063/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/pcf8563/test.nrf52-adafruit.yaml
Normal file
4
tests/components/pcf8563/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
substitutions:
|
||||
pin: GPIO1
|
||||
|
||||
<<: !include common.yaml
|
||||
8
tests/components/rx8130/common.yaml
Normal file
8
tests/components/rx8130/common.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
- rx8130.read_time
|
||||
- rx8130.write_time
|
||||
|
||||
time:
|
||||
- platform: rx8130
|
||||
i2c_id: i2c_bus
|
||||
4
tests/components/rx8130/test.esp32-idf.yaml
Normal file
4
tests/components/rx8130/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rx8130/test.esp8266-ard.yaml
Normal file
4
tests/components/rx8130/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rx8130/test.nrf52-adafruit.yaml
Normal file
4
tests/components/rx8130/test.nrf52-adafruit.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rx8130/test.rp2040-ard.yaml
Normal file
4
tests/components/rx8130/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -11,26 +11,42 @@ esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- speaker.mute_on:
|
||||
id: speaker_id
|
||||
- speaker.mute_off:
|
||||
id: speaker_id
|
||||
- if:
|
||||
condition: speaker.is_stopped
|
||||
condition:
|
||||
speaker.is_stopped:
|
||||
id: speaker_id
|
||||
then:
|
||||
- speaker.play: [0, 1, 2, 3]
|
||||
- speaker.volume_set: 0.9
|
||||
- speaker.play:
|
||||
id: speaker_id
|
||||
data: [0, 1, 2, 3]
|
||||
- speaker.volume_set:
|
||||
id: speaker_id
|
||||
volume: 0.9
|
||||
- if:
|
||||
condition: speaker.is_playing
|
||||
condition:
|
||||
speaker.is_playing:
|
||||
id: speaker_id
|
||||
then:
|
||||
- speaker.finish:
|
||||
id: speaker_id
|
||||
- speaker.stop:
|
||||
id: speaker_id
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Speaker Button"
|
||||
on_press:
|
||||
then:
|
||||
- speaker.play: [0x10, 0x20, 0x30, 0x40]
|
||||
- speaker.play: !lambda |-
|
||||
return {0x01, 0x02, (uint8_t)id(my_number).state};
|
||||
- speaker.play:
|
||||
id: speaker_id
|
||||
data: [0x10, 0x20, 0x30, 0x40]
|
||||
- speaker.play:
|
||||
id: speaker_id
|
||||
data: !lambda |-
|
||||
return {0x01, 0x02, (uint8_t)id(my_number).state};
|
||||
|
||||
i2s_audio:
|
||||
i2s_lrclk_pin: ${i2s_bclk_pin}
|
||||
|
||||
7
tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml
Normal file
7
tests/components/ssd1306_i2c/test.nrf52-xiao-ble.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
reset_pin: P0.10
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/nrf52.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,2 +0,0 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
@@ -1,8 +0,0 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO40
|
||||
sda_pin: GPIO41
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,2 +0,0 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
@@ -1,11 +1,3 @@
|
||||
remote_transmitter:
|
||||
pin: ${tx_pin}
|
||||
carrier_duty_percent: 50%
|
||||
|
||||
remote_receiver:
|
||||
id: rcvr
|
||||
pin: ${rx_pin}
|
||||
|
||||
climate:
|
||||
- platform: toshiba
|
||||
name: "RAS-2819T Climate"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO5
|
||||
rx_pin: GPIO4
|
||||
packages:
|
||||
remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-ard.yaml
|
||||
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-ard.yaml
|
||||
|
||||
<<: !include common_ras2819t.yaml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO5
|
||||
rx_pin: GPIO4
|
||||
packages:
|
||||
remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-ard.yaml
|
||||
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-c3-ard.yaml
|
||||
|
||||
<<: !include common_ras2819t.yaml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO5
|
||||
rx_pin: GPIO4
|
||||
packages:
|
||||
remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp32-idf.yaml
|
||||
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml
|
||||
|
||||
<<: !include common_ras2819t.yaml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO5
|
||||
rx_pin: GPIO4
|
||||
packages:
|
||||
remote_transmitter: !include ../../test_build_components/common/remote_transmitter/esp8266-ard.yaml
|
||||
remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml
|
||||
|
||||
<<: !include common_ras2819t.yaml
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user