1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-20 00:35:44 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
J. Nick Koston
f19bbbd1c5 Merge remote-tracking branch 'origin/parition_callbacks' into parition_callbacks 2025-11-09 23:20:01 -06:00
J. Nick Koston
0f136a888c Merge branch 'dev' into parition_callbacks and address Copilot review
- Resolved conflicts in sensor.cpp and text_sensor.cpp to keep the
  PartitionedCallbackManager approach from this branch
- Fixed platform-dependent pointer size documentation (4 bytes on 32-bit, 8 bytes on 64-bit)
- Fixed potential integer underflow in add_first comparison
- Added documentation explaining asymmetric API design rationale
2025-11-09 23:19:02 -06:00
J. Nick Koston
6feaa8dd13 preserve order 2025-11-09 23:10:06 -06:00
J. Nick Koston
4c3931363f Merge remote-tracking branch 'origin/dev' into parition_callbacks 2025-11-09 22:57:10 -06:00
J. Nick Koston
9a2fc8aa51 part 2025-11-07 23:44:43 -06:00
323 changed files with 1933 additions and 5894 deletions

View File

@@ -172,7 +172,8 @@ This document provides essential context for AI models interacting with this pro
* **C++ Class Pattern:**
```cpp
namespace esphome::my_component {
namespace esphome {
namespace my_component {
class MyComponent : public Component {
public:
@@ -188,7 +189,8 @@ This document provides essential context for AI models interacting with this pro
int param_{0};
};
} // namespace esphome::my_component
} // namespace my_component
} // namespace esphome
```
* **Common Component Examples:**

View File

@@ -22,7 +22,7 @@ jobs:
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Generate a token
id: generate-token

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0

View File

@@ -43,7 +43,7 @@ jobs:
- "docker"
# - "lint"
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:

View File

@@ -49,7 +49,7 @@ jobs:
- name: Check out code from base repository
if: steps.pr.outputs.skip != 'true'
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Always check out from the base repository (esphome/esphome), never from forks
# Use the PR's target branch to ensure we run trusted code from the main repo

View File

@@ -36,7 +36,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
@@ -70,7 +70,7 @@ jobs:
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -91,7 +91,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -132,7 +132,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
id: restore-python
uses: ./.github/actions/restore-python
@@ -183,7 +183,7 @@ jobs:
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Fetch enough history to find the merge base
fetch-depth: 2
@@ -237,7 +237,7 @@ jobs:
if: needs.determine-jobs.outputs.integration-tests == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
@@ -273,7 +273,7 @@ jobs:
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
@@ -321,7 +321,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
@@ -400,7 +400,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
@@ -489,7 +489,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
@@ -577,7 +577,7 @@ jobs:
version: 1.0
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -662,7 +662,7 @@ jobs:
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
steps:
- name: Check out code from GitHub
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -688,7 +688,7 @@ jobs:
skip: ${{ steps.check-script.outputs.skip }}
steps:
- name: Check out target branch
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.base_ref }}
@@ -840,7 +840,7 @@ jobs:
flash_usage: ${{ steps.extract.outputs.flash_usage }}
steps:
- name: Check out PR branch
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -908,7 +908,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
steps:
- name: Check out code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Restore Python
uses: ./.github/actions/restore-python
with:

View File

@@ -21,7 +21,7 @@ permissions:
jobs:
request-codeowner-reviews:
name: Run
if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }}
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- name: Request reviews from component codeowners

View File

@@ -54,11 +54,11 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: "/language:${{matrix.language}}"

View File

@@ -20,7 +20,7 @@ jobs:
branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Get tag
id: tag
# yamllint disable rule:line-length
@@ -60,7 +60,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
@@ -92,7 +92,7 @@ jobs:
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
@@ -168,7 +168,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Download digests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0

View File

@@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Checkout Home Assistant
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: home-assistant/core
path: lib/home-assistant

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.5
rev: v0.14.4
hooks:
# Run the linter.
- id: ruff

View File

@@ -206,7 +206,6 @@ esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
@@ -396,7 +395,6 @@ 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

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.12.0-dev
PROJECT_NUMBER = 2025.11.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

View File

@@ -15,11 +15,6 @@ from . import (
class MemoryAnalyzerCLI(MemoryAnalyzer):
"""Memory analyzer with CLI-specific report generation."""
# Symbol size threshold for detailed analysis
SYMBOL_SIZE_THRESHOLD: int = (
100 # Show symbols larger than this in detailed analysis
)
# Column width constants
COL_COMPONENT: int = 29
COL_FLASH_TEXT: int = 14
@@ -196,21 +191,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
f"{len(symbols):>{self.COL_CORE_COUNT}} | {percentage:>{self.COL_CORE_PERCENT - 1}.1f}%"
)
# All core symbols above threshold
# Top 15 largest core symbols
lines.append("")
lines.append(f"Top 15 Largest {_COMPONENT_CORE} Symbols:")
sorted_core_symbols = sorted(
self._esphome_core_symbols, key=lambda x: x[2], reverse=True
)
large_core_symbols = [
(symbol, demangled, size)
for symbol, demangled, size in sorted_core_symbols
if size > self.SYMBOL_SIZE_THRESHOLD
]
lines.append(
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
)
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]):
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
lines.append("=" * self.TABLE_WIDTH)
@@ -280,15 +268,13 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
lines.append(f"Total size: {comp_mem.flash_total:,} B")
lines.append("")
# Show all symbols above threshold for better visibility
# Show all symbols > 100 bytes for better visibility
large_symbols = [
(sym, dem, size)
for sym, dem, size in sorted_symbols
if size > self.SYMBOL_SIZE_THRESHOLD
(sym, dem, size) for sym, dem, size in sorted_symbols if size > 100
]
lines.append(
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
f"{comp_name} Symbols > 100 B ({len(large_symbols)} symbols):"
)
for i, (symbol, demangled, size) in enumerate(large_symbols):
lines.append(f"{i + 1}. {demangled} ({size:,} B)")

View File

@@ -476,9 +476,8 @@ 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 = &supported_modes;
msg.supported_color_modes = &traits.get_supported_color_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();
@@ -1295,11 +1294,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#endif
#ifdef USE_EVENT
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);
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);
}
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.set_event_type(StringRef(event_type));
@@ -1651,7 +1650,9 @@ 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) {
// Replace with new creator
// Clean up old creator before replacing
item.creator.cleanup(message_type);
// Move assign the new creator
item.creator = std::move(creator);
return;
}
@@ -1821,7 +1822,7 @@ void APIConnection::process_batch_() {
// Handle remaining items more efficiently
if (items_processed < this->deferred_batch_.size()) {
// Remove processed items from the beginning
// Remove processed items from the beginning with proper cleanup
this->deferred_batch_.remove_front(items_processed);
// Reschedule for remaining items
this->schedule_batch_();
@@ -1834,10 +1835,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 const char * pointer
// Special case: EventResponse uses string pointer
if (message_type == EventResponse::MESSAGE_TYPE) {
auto *e = static_cast<event::Event *>(entity);
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
}
#endif

View File

@@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection {
#endif
#ifdef USE_EVENT
void send_event(event::Event *event, const char *event_type);
void send_event(event::Event *event, const std::string &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 char *event_type, APIConnection *conn,
static uint16_t try_send_event_response(event::Event *event, const std::string &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,8 +508,10 @@ class APIConnection final : public APIServerConnection {
// Constructor for function pointer
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
// Constructor for const char * (Event types - no allocation needed)
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
// 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
// Delete copy operations - MessageCreator should only be moved
MessageCreator(const MessageCreator &other) = delete;
@@ -521,6 +523,8 @@ 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;
}
@@ -531,10 +535,20 @@ 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;
const char *const_char_ptr;
std::string *string_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
};
@@ -554,24 +568,42 @@ 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
// Clear all items with proper cleanup
void clear() {
cleanup_items_(items.size());
items.clear();
batch_start_time = 0;
}
// Remove processed items from the front
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
// 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);
}
bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }
@@ -650,30 +682,21 @@ 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) {
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
// 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()) {
// 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)) {
@@ -691,27 +714,6 @@ 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);

View File

@@ -66,7 +66,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in
static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%" PRIu64, value);
snprintf(buffer, 64, "%llu", value);
append_with_newline(out, buffer);
}

View File

@@ -1,11 +1,12 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "binary_sensor.automation";
void MultiClickTrigger::on_state_(bool state) {
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
// Handle duplicate events
if (state == this->last_state_) {
return;
@@ -66,7 +67,7 @@ void MultiClickTrigger::on_state_(bool state) {
*this->at_index_ = *this->at_index_ + 1;
}
void MultiClickTrigger::schedule_cooldown_() {
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
this->is_in_cooldown_ = true;
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
@@ -78,7 +79,7 @@ void MultiClickTrigger::schedule_cooldown_() {
this->cancel_timeout("is_valid");
this->cancel_timeout("is_not_valid");
}
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
if (min_length == 0) {
this->is_valid_ = true;
return;
@@ -89,19 +90,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
this->is_valid_ = true;
});
}
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
this->set_timeout("is_not_valid", max_length, [this]() {
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
this->is_valid_ = false;
this->schedule_cooldown_();
});
}
void MultiClickTrigger::cancel() {
void binary_sensor::MultiClickTrigger::cancel() {
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
this->is_valid_ = false;
this->schedule_cooldown_();
}
void MultiClickTrigger::trigger_() {
void binary_sensor::MultiClickTrigger::trigger_() {
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
this->at_index_.reset();
this->cancel_timeout("trigger");
@@ -117,4 +118,5 @@ bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
return length >= min_length && length <= max_length;
}
}
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -9,7 +9,8 @@
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
struct MultiClickTriggerEvent {
bool state;
@@ -171,4 +172,5 @@ template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts..
BinarySensor *sensor_;
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -3,7 +3,9 @@
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "binary_sensor";
@@ -61,4 +63,6 @@ void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
}
bool BinarySensor::is_status_binary_sensor() const { return false; }
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -6,7 +6,9 @@
#include <initializer_list>
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
class BinarySensor;
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
@@ -68,4 +70,5 @@ class BinarySensorInitiallyOff : public BinarySensor {
bool has_state() const override { return true; }
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -2,7 +2,9 @@
#include "binary_sensor.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "sensor.filter";
@@ -130,4 +132,6 @@ optional<bool> SettleFilter::new_value(bool value) {
float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -4,7 +4,9 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
class BinarySensor;
@@ -137,4 +139,6 @@ class SettleFilter : public Filter, public Component {
bool steady_{true};
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

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

View File

@@ -1,12 +1,9 @@
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,
@@ -17,10 +14,6 @@ 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]:
@@ -57,37 +50,6 @@ 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."
)
# Register socket needs for DNS server and additional HTTP connections
# - 1 UDP socket for DNS server
# - 3 additional TCP sockets for captive portal detection probes + configuration requests
# OS captive portal detection makes multiple probe requests that stay in TIME_WAIT.
# Need headroom for actual user configuration requests.
# LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts.
from esphome.components import socket
socket.consume_sockets(4, "captive_portal")(config)
return config
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])

View File

@@ -13,16 +13,14 @@ static const char *const TAG = "captive_portal";
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
char mac_s[18];
const char *mac_str = get_mac_address_pretty_into_buffer(mac_s);
#ifdef USE_ESP8266
stream->print(ESPHOME_F("{\"mac\":\""));
stream->print(mac_str);
stream->print(get_mac_address_pretty().c_str());
stream->print(ESPHOME_F("\",\"name\":\""));
stream->print(App.get_name().c_str());
stream->print(ESPHOME_F("\",\"aps\":[{}"));
#else
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", mac_str, App.get_name().c_str());
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
#endif
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
@@ -52,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
// Defer save to main loop thread to avoid NVS operations from HTTP thread
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect(ESPHOME_F("/?save"));
}
@@ -65,12 +63,6 @@ void CaptivePortal::start() {
this->base_->init();
if (!this->initialized_) {
this->base_->add_handler(this);
#ifdef USE_ESP32
// Enable LRU socket purging to handle captive portal detection probe bursts
// OS captive portal detection makes many simultaneous HTTP requests which can
// exhaust sockets. LRU purging automatically closes oldest idle connections.
this->base_->get_server()->set_lru_purge_enable(true);
#endif
}
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();

View File

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

View File

@@ -4,7 +4,8 @@
#include "esphome/core/automation.h"
#include "cover.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
@@ -130,4 +131,5 @@ class CoverClosedTrigger : public Trigger<> {
}
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -6,7 +6,8 @@
#include "esphome/core/log.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
static const char *const TAG = "cover";
@@ -211,4 +212,5 @@ void CoverRestoreState::apply(Cover *cover) {
cover->publish_state();
}
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -7,7 +7,8 @@
#include "cover_traits.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
const extern float COVER_OPEN;
const extern float COVER_CLOSED;
@@ -156,4 +157,5 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
ESPPreferenceObject rtc_;
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#pragma once
namespace esphome::cover {
namespace esphome {
namespace cover {
class CoverTraits {
public:
@@ -25,4 +26,5 @@ class CoverTraits {
bool supports_stop_{false};
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -3,10 +3,10 @@
namespace esphome {
namespace dashboard_import {
static const char *g_package_import_url = ""; // NOLINT
static std::string g_package_import_url; // NOLINT
const char *get_package_import_url() { return g_package_import_url; }
void set_package_import_url(const char *url) { g_package_import_url = url; }
const std::string &get_package_import_url() { return g_package_import_url; }
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
} // namespace dashboard_import
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#pragma once
#include <string>
namespace esphome {
namespace dashboard_import {
const char *get_package_import_url();
void set_package_import_url(const char *url);
const std::string &get_package_import_url();
void set_package_import_url(std::string url);
} // namespace dashboard_import
} // namespace esphome

View File

@@ -49,9 +49,9 @@ void DebugComponent::dump_config() {
}
#endif // USE_TEXT_SENSOR
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
this->log_partition_info_(); // Log partition information
#endif
#ifdef USE_ESP32
this->log_partition_info_(); // Log partition information for ESP32
#endif // USE_ESP32
}
void DebugComponent::loop() {

View File

@@ -62,19 +62,19 @@ class DebugComponent : public PollingComponent {
sensor::Sensor *cpu_frequency_sensor_{nullptr};
#endif // USE_SENSOR
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
#ifdef USE_ESP32
/**
* @brief Logs information about the device's partition table.
*
* This function iterates through the partition table and logs details
* This function iterates through the ESP32's 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 and ZEPHYR platforms.
* Only available when compiled for ESP32 platforms.
*/
void log_partition_info_();
#endif
#endif // USE_ESP32
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *device_info_{nullptr};

View File

@@ -5,7 +5,6 @@
#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]
@@ -87,37 +86,6 @@ 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) {

View File

@@ -23,7 +23,7 @@ void DS1307Component::dump_config() {
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
RealTimeClock::dump_config();
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -102,7 +102,7 @@ def customise_schema(config):
"""
config = cv.Schema(
{
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"),
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
},
extra=cv.ALLOW_EXTRA,
)(config)

View File

@@ -32,15 +32,11 @@ class SpectraE6(EpaperModel):
spectra_e6 = SpectraE6("spectra-e6")
spectra_e6_7p3 = spectra_e6.extend(
"7.3in-Spectra-E6",
spectra_e6.extend(
"Seeed-reTerminal-E1002",
width=800,
height=480,
data_rate="20MHz",
)
spectra_e6_7p3.extend(
"Seeed-reTerminal-E1002",
cs_pin=10,
dc_pin=11,
reset_pin=12,

View File

@@ -334,14 +334,12 @@ 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, 4),
"dev": cv.Version(3, 3, 4),
"latest": cv.Version(3, 3, 2),
"dev": cv.Version(3, 3, 2),
}
ARDUINO_PLATFORM_VERSION_LOOKUP = {
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, 2): cv.Version(55, 3, 31, "1"),
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"),
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),
@@ -359,8 +357,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, "2"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
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, 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"),
@@ -375,15 +373,14 @@ 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, "2"),
"latest": cv.Version(55, 3, 31, "2"),
"dev": cv.Version(55, 3, 31, "2"),
"recommended": cv.Version(55, 3, 31, "1"),
"latest": cv.Version(55, 3, 31, "1"),
"dev": cv.Version(55, 3, 31, "1"),
}
def _check_versions(config):
config = config.copy()
value = config[CONF_FRAMEWORK]
def _check_versions(value):
value = value.copy()
if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP:
if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value:
@@ -448,7 +445,7 @@ def _check_versions(config):
"If there are connectivity or build issues please remove the manual version."
)
return config
return value
def _parse_platform_version(value):
@@ -498,8 +495,6 @@ 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"
@@ -516,14 +511,22 @@ def final_validate(config):
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
)
)
if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
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]
):
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 advanced[CONF_EXECUTE_FROM_PSRAM]:
if (
config.get(CONF_FRAMEWORK, {})
.get(CONF_ADVANCED, {})
.get(CONF_EXECUTE_FROM_PSRAM)
):
if config[CONF_VARIANT] != VARIANT_ESP32S3:
errs.append(
cv.Invalid(
@@ -539,17 +542,6 @@ 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)
@@ -604,74 +596,89 @@ def _validate_idf_component(config: ConfigType) -> ConfigType:
FRAMEWORK_ESP_IDF = "esp-idf"
FRAMEWORK_ARDUINO = "arduino"
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,
)
),
}
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,
)
@@ -734,11 +741,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[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
if CONF_TYPE not in config[CONF_FRAMEWORK]:
config = config.copy()
variant = config[CONF_VARIANT]
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
if variant in ARDUINO_ALLOWED_VARIANTS:
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
_show_framework_migration_message(
@@ -778,7 +785,6 @@ CONFIG_SCHEMA = cv.All(
),
_detect_variant,
_set_default_framework,
_check_versions,
set_core_data,
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
)
@@ -797,7 +803,9 @@ 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[CONF_SDKCONFIG_OPTIONS].get("CONFIG_LWIP_MAX_SOCKETS")
user_max_sockets = conf.get(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())
@@ -931,12 +939,6 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2:
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-Wno-nonnull-compare")
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
@@ -973,18 +975,23 @@ 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 advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not (
conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations
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
)
):
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]:
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]:
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
_configure_lwip_max_sockets(conf)
if advanced[CONF_EXECUTE_FROM_PSRAM]:
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
@@ -995,22 +1002,23 @@ 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[CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING]:
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
if advanced[CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY]:
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
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[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]:
if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True):
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[CONF_DISABLE_VFS_SUPPORT_TERMIOS]
"CONFIG_VFS_SUPPORT_TERMIOS",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True),
)
# Disable VFS support for select() with file descriptors
@@ -1024,7 +1032,8 @@ 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[CONF_DISABLE_VFS_SUPPORT_SELECT]
"CONFIG_VFS_SUPPORT_SELECT",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True),
)
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
@@ -1037,7 +1046,8 @@ 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[CONF_DISABLE_VFS_SUPPORT_DIR]
"CONFIG_VFS_SUPPORT_DIR",
not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True),
)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
@@ -1051,7 +1061,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[CONF_COMPILER_OPTIMIZATION]
compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION)
for key, flag in COMPILER_OPTIMIZATIONS.items():
add_idf_sdkconfig_option(flag, compiler_optimization == key)
@@ -1060,20 +1070,18 @@ async def to_code(config):
conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT],
)
if advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
if advanced.get(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[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]:
if advanced.get(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[CONF_LOOP_TASK_STACK_SIZE])
cg.add_define(
"ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE)
)
cg.add_define(
"USE_ESP_IDF_VERSION_CODE",

View File

@@ -634,13 +634,11 @@ 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",
mac_s, io_capability_s);
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
}

View File

@@ -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(std::initializer_list<uint64_t> addresses) { this->address_vec_ = addresses; }
void set_addresses(const std::vector<uint64_t> &addresses) { this->address_vec_ = addresses; }
bool parse_device(const ESPBTDevice &device) override {
uint64_t u64_addr = device.address_uint64();

View File

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

View File

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

View File

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

View File

@@ -381,10 +381,7 @@ void EthernetComponent::dump_config() {
break;
}
ESP_LOGCONFIG(TAG,
"Ethernet:\n"
" Connected: %s",
YESNO(this->is_connected()));
ESP_LOGCONFIG(TAG, "Ethernet:");
this->dump_connect_params_();
#ifdef USE_ETHERNET_SPI
ESP_LOGCONFIG(TAG,
@@ -421,6 +418,8 @@ 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;

View File

@@ -58,6 +58,7 @@ 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();

View File

@@ -36,6 +36,7 @@ from esphome.const import (
CONF_WEIGHT,
)
from esphome.core import CORE, HexInt
from esphome.helpers import cpp_string_escape
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -49,6 +50,7 @@ font_ns = cg.esphome_ns.namespace("font")
Font = font_ns.class_("Font")
Glyph = font_ns.class_("Glyph")
GlyphData = font_ns.struct("GlyphData")
CONF_BPP = "bpp"
CONF_EXTRAS = "extras"
@@ -461,7 +463,7 @@ FONT_SCHEMA = cv.Schema(
)
),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(Glyph),
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
},
)
@@ -486,8 +488,6 @@ class GlyphInfo:
def glyph_to_glyphinfo(glyph, font, size, bpp):
# Convert to 32 bit unicode codepoint
glyph = ord(glyph)
scale = 256 // (1 << bpp)
if not font.is_scalable:
sizes = [pt_to_px(x.size) for x in font.available_sizes]
@@ -583,15 +583,22 @@ async def to_code(config):
# Create the glyph table that points to data in the above array.
glyph_initializer = [
[
x.glyph,
prog_arr + (y - len(x.bitmap_data)),
x.advance,
x.offset_x,
x.offset_y,
x.width,
x.height,
]
cg.StructInitializer(
GlyphData,
(
"a_char",
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
),
(
"data",
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
),
("advance", x.advance),
("offset_x", x.offset_x),
("offset_y", x.offset_y),
("width", x.width),
("height", x.height),
)
for (x, y) in zip(
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
)

View File

@@ -6,245 +6,133 @@
namespace esphome {
namespace font {
static const char *const TAG = "font";
#ifdef USE_LVGL_FONT
const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
auto *fe = (Font *) font->dsc;
const auto *gd = fe->get_glyph_data_(unicode_letter);
if (gd == nullptr) {
return nullptr;
const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; }
// Compare the char at the string position with this char.
// Return true if this char is less than or equal the other.
bool Glyph::compare_to(const uint8_t *str) const {
// 1 -> this->char_
// 2 -> str
for (uint32_t i = 0;; i++) {
if (this->glyph_data_->a_char[i] == '\0')
return true;
if (str[i] == '\0')
return false;
if (this->glyph_data_->a_char[i] > str[i])
return false;
if (this->glyph_data_->a_char[i] < str[i])
return true;
}
return gd->data;
// this should not happen
return false;
}
int Glyph::match_length(const uint8_t *str) const {
for (uint32_t i = 0;; i++) {
if (this->glyph_data_->a_char[i] == '\0')
return i;
if (str[i] != this->glyph_data_->a_char[i])
return 0;
}
// this should not happen
return 0;
}
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
*x1 = this->glyph_data_->offset_x;
*y1 = this->glyph_data_->offset_y;
*width = this->glyph_data_->width;
*height = this->glyph_data_->height;
}
bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
auto *fe = (Font *) font->dsc;
const auto *gd = fe->get_glyph_data_(unicode_letter);
if (gd == nullptr) {
return false;
}
dsc->adv_w = gd->advance;
dsc->ofs_x = gd->offset_x;
dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
dsc->box_w = gd->width;
dsc->box_h = gd->height;
dsc->is_placeholder = 0;
dsc->bpp = fe->get_bpp();
return true;
}
const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
return this->last_data_;
auto *glyph = this->find_glyph(unicode_letter);
if (glyph == nullptr) {
return nullptr;
}
this->last_data_ = glyph;
this->last_letter_ = unicode_letter;
return glyph;
}
#endif
/**
* Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string.
* If successful, return the codepoint and set the length to the number of bytes read.
* If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to
* 0.
*
* @param utf8_str The input string
* @param length Pointer to length storage
* @return The extracted code point
*/
static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
// Safely cast to uint8_t* for correct bitwise operations on bytes
const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
uint32_t code_point = 0;
uint8_t c1 = *current++;
// check for end of string
if (c1 == 0) {
*length = 0;
return 0;
}
// --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
if (c1 < 0x80) {
// Valid ASCII byte.
code_point = c1;
// Optimization: No need to check for continuation bytes.
}
// --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
else if ((c1 & 0xE0) == 0xC0) {
uint8_t c2 = *current++;
// Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
if ((c2 & 0xC0) != 0x80) {
*length = 0;
return 0;
}
code_point = (c1 & 0x1F) << 6;
code_point |= (c2 & 0x3F);
// Error Check 2: Overlong check (2-byte must be > 0x7F)
if (code_point <= 0x7F) {
*length = 0;
return 0;
}
}
// --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
else if ((c1 & 0xF0) == 0xE0) {
uint8_t c2 = *current++;
uint8_t c3 = *current++;
// Error Check 1: Check continuation bytes
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
*length = 0;
return 0;
}
code_point = (c1 & 0x0F) << 12;
code_point |= (c2 & 0x3F) << 6;
code_point |= (c3 & 0x3F);
// Error Check 2: Overlong check (3-byte must be > 0x7FF)
// Also check for surrogates (0xD800-0xDFFF)
if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
*length = 0;
return 0;
}
}
// --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
else if ((c1 & 0xF8) == 0xF0) {
uint8_t c2 = *current++;
uint8_t c3 = *current++;
uint8_t c4 = *current++;
// Error Check 1: Check continuation bytes
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
*length = 0;
return 0;
}
code_point = (c1 & 0x07) << 18;
code_point |= (c2 & 0x3F) << 12;
code_point |= (c3 & 0x3F) << 6;
code_point |= (c4 & 0x3F);
// Error Check 2: Overlong check (4-byte must be > 0xFFFF)
// Also check for valid Unicode range (must be <= 0x10FFFF)
if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
*length = 0;
return 0;
}
}
// --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
else {
*length = 0;
return 0;
}
*length = current - reinterpret_cast<const uint8_t *>(utf8_str);
return code_point;
}
Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp)
: glyphs_(ConstVector(data, data_nr)),
baseline_(baseline),
: baseline_(baseline),
height_(height),
descender_(descender),
linegap_(height - baseline - descender),
xheight_(xheight),
capheight_(capheight),
bpp_(bpp) {
#ifdef USE_LVGL_FONT
this->lv_font_.dsc = this;
this->lv_font_.line_height = this->get_height();
this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
this->lv_font_.underline_position = -1;
this->lv_font_.underline_thickness = 1;
#endif
glyphs_.reserve(data_nr);
for (int i = 0; i < data_nr; ++i)
glyphs_.emplace_back(&data[i]);
}
const Glyph *Font::find_glyph(uint32_t codepoint) const {
int Font::match_next_glyph(const uint8_t *str, int *match_length) {
int lo = 0;
int hi = this->glyphs_.size() - 1;
while (lo != hi) {
int mid = (lo + hi + 1) / 2;
if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
if (this->glyphs_[mid].compare_to(str)) {
lo = mid;
} else {
hi = mid - 1;
}
}
auto *result = &this->glyphs_[lo];
if (result->code_point == codepoint)
return result;
return nullptr;
*match_length = this->glyphs_[lo].match_length(str);
if (*match_length <= 0)
return -1;
return lo;
}
#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
*baseline = this->baseline_;
*height = this->height_;
int i = 0;
int min_x = 0;
bool has_char = false;
int x = 0;
for (;;) {
size_t length;
auto code_point = extract_unicode_codepoint(str, &length);
if (length == 0)
break;
str += length;
auto *glyph = this->find_glyph(code_point);
if (glyph == nullptr) {
while (str[i] != '\0') {
int match_length;
int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length);
if (glyph_n < 0) {
// Unknown char, skip
if (!this->glyphs_.empty())
x += this->glyphs_[0].advance;
if (!this->get_glyphs().empty())
x += this->get_glyphs()[0].glyph_data_->advance;
i++;
continue;
}
const Glyph &glyph = this->glyphs_[glyph_n];
if (!has_char) {
min_x = glyph->offset_x;
min_x = glyph.glyph_data_->offset_x;
} else {
min_x = std::min(min_x, x + glyph->offset_x);
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
}
x += glyph->advance;
x += glyph.glyph_data_->advance;
i += match_length;
has_char = true;
}
*x_offset = min_x;
*width = x - min_x;
}
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
int i = 0;
int x_at = x_start;
for (;;) {
size_t length;
auto code_point = extract_unicode_codepoint(text, &length);
if (length == 0)
break;
text += length;
auto *glyph = this->find_glyph(code_point);
if (glyph == nullptr) {
int scan_x1, scan_y1, scan_width, scan_height;
while (text[i] != '\0') {
int match_length;
int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length);
if (glyph_n < 0) {
// Unknown char, skip
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
if (!this->glyphs_.empty()) {
uint8_t glyph_width = this->glyphs_[0].advance;
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
if (!this->get_glyphs().empty()) {
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance;
display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color);
x_at += glyph_width;
}
i++;
continue;
}
const uint8_t *data = glyph->data;
const int max_x = x_at + glyph->offset_x + glyph->width;
const int max_y = y_start + glyph->offset_y + glyph->height;
const Glyph &glyph = this->get_glyphs()[glyph_n];
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
const uint8_t *data = glyph.glyph_data_->data;
const int max_x = x_at + scan_x1 + scan_width;
const int max_y = y_start + scan_y1 + scan_height;
uint8_t bitmask = 0;
uint8_t pixel_data = 0;
@@ -257,10 +145,10 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
auto b_g = (float) background.g;
auto b_b = (float) background.b;
auto b_w = (float) background.w;
for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
uint8_t pixel = 0;
for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
for (int bit_num = 0; bit_num != this->bpp_; bit_num++) {
if (bitmask == 0) {
pixel_data = progmem_read_byte(data++);
bitmask = 0x80;
@@ -280,9 +168,12 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
}
}
}
x_at += glyph->advance;
x_at += glyph.glyph_data_->advance;
i += match_length;
}
}
#endif
} // namespace font
} // namespace esphome

View File

@@ -6,30 +6,14 @@
#ifdef USE_DISPLAY
#include "esphome/components/display/display.h"
#endif
#ifdef USE_LVGL_FONT
#include <lvgl.h>
#endif
namespace esphome {
namespace font {
class Font;
class Glyph {
public:
constexpr Glyph(uint32_t code_point, const uint8_t *data, int advance, int offset_x, int offset_y, int width,
int height)
: code_point(code_point),
data(data),
advance(advance),
offset_x(offset_x),
offset_y(offset_y),
width(width),
height(height) {}
bool is_less_or_equal(uint32_t other) const { return this->code_point <= other; }
const uint32_t code_point;
struct GlyphData {
const uint8_t *a_char;
const uint8_t *data;
int advance;
int offset_x;
@@ -38,6 +22,26 @@ class Glyph {
int height;
};
class Glyph {
public:
Glyph(const GlyphData *data) : glyph_data_(data) {}
const uint8_t *get_char() const;
bool compare_to(const uint8_t *str) const;
int match_length(const uint8_t *str) const;
void scan_area(int *x1, int *y1, int *width, int *height) const;
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
protected:
friend Font;
const GlyphData *glyph_data_;
};
class Font
#ifdef USE_DISPLAY
: public display::BaseFont
@@ -46,8 +50,8 @@ class Font
public:
/** Construct the font with the given glyphs.
*
* @param data A list of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs
* @param data A vector of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs in data.
* @param baseline The y-offset from the top of the text to the baseline.
* @param height The y-offset from the top of the text to the bottom.
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
@@ -55,10 +59,10 @@ class Font
* @param capheight The height of capital letters, usually measured at the "X" glyph.
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
*/
Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp = 1);
const Glyph *find_glyph(uint32_t codepoint) const;
int match_next_glyph(const uint8_t *str, int *match_length);
#ifdef USE_DISPLAY
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
@@ -73,14 +77,11 @@ class Font
inline int get_xheight() { return this->xheight_; }
inline int get_capheight() { return this->capheight_; }
inline int get_bpp() { return this->bpp_; }
#ifdef USE_LVGL_FONT
const lv_font_t *get_lv_font() const { return &this->lv_font_; }
#endif
const ConstVector<Glyph> &get_glyphs() const { return glyphs_; }
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
protected:
ConstVector<Glyph> glyphs_;
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
int baseline_;
int height_;
int descender_;
@@ -88,14 +89,6 @@ class Font
int xheight_;
int capheight_;
uint8_t bpp_; // bits per pixel
#ifdef USE_LVGL_FONT
lv_font_t lv_font_{};
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter);
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next);
const Glyph *get_glyph_data_(uint32_t unicode_letter);
uint32_t last_letter_{};
const Glyph *last_data_{};
#endif
};
} // namespace font

View File

@@ -1,247 +0,0 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_ID,
CONF_NAME,
CONF_ON_ENROLLMENT_DONE,
CONF_ON_ENROLLMENT_FAILED,
CONF_TRIGGER_ID,
)
CODEOWNERS = ["@OnFreund"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"]
MULTI_CONF = True
CONF_HLK_FM22X_ID = "hlk_fm22x_id"
CONF_FACE_ID = "face_id"
CONF_ON_FACE_SCAN_MATCHED = "on_face_scan_matched"
CONF_ON_FACE_SCAN_UNMATCHED = "on_face_scan_unmatched"
CONF_ON_FACE_SCAN_INVALID = "on_face_scan_invalid"
CONF_ON_FACE_INFO = "on_face_info"
hlk_fm22x_ns = cg.esphome_ns.namespace("hlk_fm22x")
HlkFm22xComponent = hlk_fm22x_ns.class_(
"HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice
)
FaceScanMatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string)
)
FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanUnmatchedTrigger", automation.Trigger.template()
)
FaceScanInvalidTrigger = hlk_fm22x_ns.class_(
"FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8)
)
FaceInfoTrigger = hlk_fm22x_ns.class_(
"FaceInfoTrigger",
automation.Trigger.template(
cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16
),
)
EnrollmentDoneTrigger = hlk_fm22x_ns.class_(
"EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8)
)
EnrollmentFailedTrigger = hlk_fm22x_ns.class_(
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8)
)
EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action)
DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action)
DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action)
ScanAction = hlk_fm22x_ns.class_("ScanAction", automation.Action)
ResetAction = hlk_fm22x_ns.class_("ResetAction", automation.Action)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HlkFm22xComponent),
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanMatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanUnmatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanInvalidTrigger
),
}
),
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger),
}
),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentDoneTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentFailedTrigger
),
}
),
}
)
.extend(cv.polling_component_schema("50ms"))
.extend(uart.UART_DEVICE_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf
)
for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
for conf in config.get(CONF_ON_FACE_INFO, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger,
[
(cg.int16, "status"),
(cg.int16, "left"),
(cg.int16, "top"),
(cg.int16, "right"),
(cg.int16, "bottom"),
(cg.int16, "yaw"),
(cg.int16, "pitch"),
(cg.int16, "roll"),
],
conf,
)
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf
)
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
@automation.register_action(
"hlk_fm22x.enroll",
EnrollmentAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
cv.Required(CONF_NAME): cv.templatable(cv.string),
cv.Required(CONF_DIRECTION): cv.templatable(cv.uint8_t),
},
key=CONF_NAME,
),
)
async def hlk_fm22x_enroll_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_NAME], args, cg.std_string)
cg.add(var.set_name(template_))
template_ = await cg.templatable(config[CONF_DIRECTION], args, cg.uint8)
cg.add(var.set_direction(template_))
return var
@automation.register_action(
"hlk_fm22x.delete",
DeleteAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
cv.Required(CONF_FACE_ID): cv.templatable(cv.uint16_t),
},
key=CONF_FACE_ID,
),
)
async def hlk_fm22x_delete_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_FACE_ID], args, cg.int16)
cg.add(var.set_face_id(template_))
return var
@automation.register_action(
"hlk_fm22x.delete_all",
DeleteAllAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_delete_all_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(
"hlk_fm22x.scan",
ScanAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_scan_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(
"hlk_fm22x.reset",
ResetAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_reset_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

View File

@@ -1,21 +0,0 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ICON, ICON_KEY_PLUS
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon,
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
var = await binary_sensor.new_binary_sensor(config)
cg.add(hub.set_enrolling_binary_sensor(var))

View File

@@ -1,325 +0,0 @@
#include "hlk_fm22x.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <array>
#include <cinttypes>
namespace esphome::hlk_fm22x {
static const char *const TAG = "hlk_fm22x";
void HlkFm22xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
this->set_enrolling_(false);
while (this->available()) {
this->read();
}
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
}
void HlkFm22xComponent::update() {
if (this->active_command_ != HlkFm22xCommand::NONE) {
if (this->wait_cycles_ > 600) {
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
if (HlkFm22xCommand::RESET == this->active_command_) {
this->mark_failed();
} else {
this->reset();
}
}
}
this->recv_command_();
}
void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) {
if (name.length() > 31) {
ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str());
return;
}
ESP_LOGI(TAG, "Starting enrollment for %s", name.c_str());
std::array<uint8_t, 35> data{};
data[0] = 0; // admin
std::copy(name.begin(), name.end(), data.begin() + 1);
// Remaining bytes are already zero-initialized
data[33] = (uint8_t) direction;
data[34] = 10; // timeout
this->send_command_(HlkFm22xCommand::ENROLL, data.data(), data.size());
this->set_enrolling_(true);
}
void HlkFm22xComponent::scan_face() {
ESP_LOGI(TAG, "Verify face");
static const uint8_t DATA[] = {0, 0};
this->send_command_(HlkFm22xCommand::VERIFY, DATA, sizeof(DATA));
}
void HlkFm22xComponent::delete_face(int16_t face_id) {
ESP_LOGI(TAG, "Deleting face in slot %d", face_id);
const uint8_t data[] = {(uint8_t) (face_id >> 8), (uint8_t) (face_id & 0xFF)};
this->send_command_(HlkFm22xCommand::DELETE_FACE, data, sizeof(data));
}
void HlkFm22xComponent::delete_all_faces() {
ESP_LOGI(TAG, "Deleting all stored faces");
this->send_command_(HlkFm22xCommand::DELETE_ALL_FACES);
}
void HlkFm22xComponent::get_face_count_() {
ESP_LOGD(TAG, "Getting face count");
this->send_command_(HlkFm22xCommand::GET_ALL_FACE_IDS);
}
void HlkFm22xComponent::reset() {
ESP_LOGI(TAG, "Resetting module");
this->active_command_ = HlkFm22xCommand::NONE;
this->wait_cycles_ = 0;
this->set_enrolling_(false);
this->send_command_(HlkFm22xCommand::RESET);
}
void HlkFm22xComponent::send_command_(HlkFm22xCommand command, const uint8_t *data, size_t size) {
ESP_LOGV(TAG, "Send command: 0x%.2X", command);
if (this->active_command_ != HlkFm22xCommand::NONE) {
ESP_LOGW(TAG, "Command 0x%.2X already active", this->active_command_);
return;
}
this->wait_cycles_ = 0;
this->active_command_ = command;
while (this->available())
this->read();
this->write((uint8_t) (START_CODE >> 8));
this->write((uint8_t) (START_CODE & 0xFF));
this->write((uint8_t) command);
uint16_t data_size = size;
this->write((uint8_t) (data_size >> 8));
this->write((uint8_t) (data_size & 0xFF));
uint8_t checksum = 0;
checksum ^= (uint8_t) command;
checksum ^= (data_size >> 8);
checksum ^= (data_size & 0xFF);
for (size_t i = 0; i < size; i++) {
this->write(data[i]);
checksum ^= data[i];
}
this->write(checksum);
this->active_command_ = command;
this->wait_cycles_ = 0;
}
void HlkFm22xComponent::recv_command_() {
uint8_t byte, checksum = 0;
uint16_t length = 0;
if (this->available() < 7) {
++this->wait_cycles_;
return;
}
this->wait_cycles_ = 0;
if ((this->read() != (uint8_t) (START_CODE >> 8)) || (this->read() != (uint8_t) (START_CODE & 0xFF))) {
ESP_LOGE(TAG, "Invalid start code");
return;
}
byte = this->read();
checksum ^= byte;
HlkFm22xResponseType response_type = (HlkFm22xResponseType) byte;
byte = this->read();
checksum ^= byte;
length = byte << 8;
byte = this->read();
checksum ^= byte;
length |= byte;
std::vector<uint8_t> data;
data.reserve(length);
for (uint16_t idx = 0; idx < length; ++idx) {
byte = this->read();
checksum ^= byte;
data.push_back(byte);
}
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str());
byte = this->read();
if (byte != checksum) {
ESP_LOGE(TAG, "Invalid checksum for data. Calculated: 0x%.2X, Received: 0x%.2X", checksum, byte);
return;
}
switch (response_type) {
case HlkFm22xResponseType::NOTE:
this->handle_note_(data);
break;
case HlkFm22xResponseType::REPLY:
this->handle_reply_(data);
break;
default:
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
break;
}
}
void HlkFm22xComponent::handle_note_(const std::vector<uint8_t> &data) {
switch (data[0]) {
case HlkFm22xNoteType::FACE_STATE:
if (data.size() < 17) {
ESP_LOGE(TAG, "Invalid face note data size: %u", data.size());
break;
}
{
int16_t info[8];
uint8_t offset = 1;
for (int16_t &i : info) {
i = ((int16_t) data[offset + 1] << 8) | data[offset];
offset += 2;
}
ESP_LOGV(TAG, "Face state: status: %d, left: %d, top: %d, right: %d, bottom: %d, yaw: %d, pitch: %d, roll: %d",
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
this->face_info_callback_.call(info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
}
break;
case HlkFm22xNoteType::READY:
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
switch (this->active_command_) {
case HlkFm22xCommand::ENROLL:
this->set_enrolling_(false);
this->enrollment_failed_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
break;
case HlkFm22xCommand::VERIFY:
this->face_scan_invalid_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
break;
default:
break;
}
this->active_command_ = HlkFm22xCommand::NONE;
this->wait_cycles_ = 0;
break;
default:
ESP_LOGW(TAG, "Unhandled note: 0x%.2X", data[0]);
break;
}
}
void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
auto expected = this->active_command_;
this->active_command_ = HlkFm22xCommand::NONE;
if (data[0] != (uint8_t) expected) {
ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]);
return;
}
if (data[1] != HlkFm22xResult::SUCCESS) {
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
switch (expected) {
case HlkFm22xCommand::ENROLL:
this->set_enrolling_(false);
this->enrollment_failed_callback_.call(data[1]);
break;
case HlkFm22xCommand::VERIFY:
if (data[1] == HlkFm22xResult::REJECTED) {
this->face_scan_unmatched_callback_.call();
} else {
this->face_scan_invalid_callback_.call(data[1]);
}
break;
default:
break;
}
return;
}
switch (expected) {
case HlkFm22xCommand::VERIFY: {
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
std::string name(data.begin() + 4, data.begin() + 36);
ESP_LOGD(TAG, "Face verified. ID: %d, name: %s", face_id, name.c_str());
if (this->last_face_id_sensor_ != nullptr) {
this->last_face_id_sensor_->publish_state(face_id);
}
if (this->last_face_name_text_sensor_ != nullptr) {
this->last_face_name_text_sensor_->publish_state(name);
}
this->face_scan_matched_callback_.call(face_id, name);
break;
}
case HlkFm22xCommand::ENROLL: {
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
HlkFm22xFaceDirection direction = (HlkFm22xFaceDirection) data[4];
ESP_LOGI(TAG, "Face enrolled. ID: %d, Direction: 0x%.2X", face_id, direction);
this->enrollment_done_callback_.call(face_id, (uint8_t) direction);
this->set_enrolling_(false);
this->defer([this]() { this->get_face_count_(); });
break;
}
case HlkFm22xCommand::GET_STATUS:
if (this->status_sensor_ != nullptr) {
this->status_sensor_->publish_state(data[2]);
}
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); });
break;
case HlkFm22xCommand::GET_VERSION:
if (this->version_text_sensor_ != nullptr) {
std::string version(data.begin() + 2, data.end());
this->version_text_sensor_->publish_state(version);
}
this->defer([this]() { this->get_face_count_(); });
break;
case HlkFm22xCommand::GET_ALL_FACE_IDS:
if (this->face_count_sensor_ != nullptr) {
this->face_count_sensor_->publish_state(data[2]);
}
break;
case HlkFm22xCommand::DELETE_FACE:
ESP_LOGI(TAG, "Deleted face");
break;
case HlkFm22xCommand::DELETE_ALL_FACES:
ESP_LOGI(TAG, "Deleted all faces");
break;
case HlkFm22xCommand::RESET:
ESP_LOGI(TAG, "Module reset");
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
break;
default:
ESP_LOGW(TAG, "Unhandled command: 0x%.2X", this->active_command_);
break;
}
}
void HlkFm22xComponent::set_enrolling_(bool enrolling) {
if (this->enrolling_binary_sensor_ != nullptr) {
this->enrolling_binary_sensor_->publish_state(enrolling);
}
}
void HlkFm22xComponent::dump_config() {
ESP_LOGCONFIG(TAG, "HLK_FM22X:");
LOG_UPDATE_INTERVAL(this);
if (this->version_text_sensor_) {
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->version_text_sensor_->get_state().c_str());
}
if (this->enrolling_binary_sensor_) {
LOG_BINARY_SENSOR(" ", "Enrolling", this->enrolling_binary_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->enrolling_binary_sensor_->state ? "ON" : "OFF");
}
if (this->face_count_sensor_) {
LOG_SENSOR(" ", "Face Count", this->face_count_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->face_count_sensor_->get_state());
}
if (this->status_sensor_) {
LOG_SENSOR(" ", "Status", this->status_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
}
if (this->last_face_id_sensor_) {
LOG_SENSOR(" ", "Last Face ID", this->last_face_id_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (int16_t) this->last_face_id_sensor_->get_state());
}
if (this->last_face_name_text_sensor_) {
LOG_TEXT_SENSOR(" ", "Last Face Name", this->last_face_name_text_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->last_face_name_text_sensor_->get_state().c_str());
}
}
} // namespace esphome::hlk_fm22x

View File

@@ -1,224 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
#include <utility>
#include <vector>
namespace esphome::hlk_fm22x {
static const uint16_t START_CODE = 0xEFAA;
enum HlkFm22xCommand {
NONE = 0x00,
RESET = 0x10,
GET_STATUS = 0x11,
VERIFY = 0x12,
ENROLL = 0x13,
DELETE_FACE = 0x20,
DELETE_ALL_FACES = 0x21,
GET_ALL_FACE_IDS = 0x24,
GET_VERSION = 0x30,
GET_SERIAL_NUMBER = 0x93,
};
enum HlkFm22xResponseType {
REPLY = 0x00,
NOTE = 0x01,
IMAGE = 0x02,
};
enum HlkFm22xNoteType {
READY = 0x00,
FACE_STATE = 0x01,
};
enum HlkFm22xResult {
SUCCESS = 0x00,
REJECTED = 0x01,
ABORTED = 0x02,
FAILED4_CAMERA = 0x04,
FAILED4_UNKNOWNREASON = 0x05,
FAILED4_INVALIDPARAM = 0x06,
FAILED4_NOMEMORY = 0x07,
FAILED4_UNKNOWNUSER = 0x08,
FAILED4_MAXUSER = 0x09,
FAILED4_FACEENROLLED = 0x0A,
FAILED4_LIVENESSCHECK = 0x0C,
FAILED4_TIMEOUT = 0x0D,
FAILED4_AUTHORIZATION = 0x0E,
FAILED4_READ_FILE = 0x13,
FAILED4_WRITE_FILE = 0x14,
FAILED4_NO_ENCRYPT = 0x15,
FAILED4_NO_RGBIMAGE = 0x17,
FAILED4_JPGPHOTO_LARGE = 0x18,
FAILED4_JPGPHOTO_SMALL = 0x19,
};
enum HlkFm22xFaceDirection {
FACE_DIRECTION_UNDEFINED = 0x00,
FACE_DIRECTION_MIDDLE = 0x01,
FACE_DIRECTION_RIGHT = 0x02,
FACE_DIRECTION_LEFT = 0x04,
FACE_DIRECTION_DOWN = 0x08,
FACE_DIRECTION_UP = 0x10,
};
class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
void set_face_count_sensor(sensor::Sensor *face_count_sensor) { this->face_count_sensor_ = face_count_sensor; }
void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; }
void set_last_face_id_sensor(sensor::Sensor *last_face_id_sensor) {
this->last_face_id_sensor_ = last_face_id_sensor;
}
void set_last_face_name_text_sensor(text_sensor::TextSensor *last_face_name_text_sensor) {
this->last_face_name_text_sensor_ = last_face_name_text_sensor;
}
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
}
void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) {
this->version_text_sensor_ = version_text_sensor;
}
void add_on_face_scan_matched_callback(std::function<void(int16_t, std::string)> callback) {
this->face_scan_matched_callback_.add(std::move(callback));
}
void add_on_face_scan_unmatched_callback(std::function<void()> callback) {
this->face_scan_unmatched_callback_.add(std::move(callback));
}
void add_on_face_scan_invalid_callback(std::function<void(uint8_t)> callback) {
this->face_scan_invalid_callback_.add(std::move(callback));
}
void add_on_face_info_callback(
std::function<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> callback) {
this->face_info_callback_.add(std::move(callback));
}
void add_on_enrollment_done_callback(std::function<void(int16_t, uint8_t)> callback) {
this->enrollment_done_callback_.add(std::move(callback));
}
void add_on_enrollment_failed_callback(std::function<void(uint8_t)> callback) {
this->enrollment_failed_callback_.add(std::move(callback));
}
void enroll_face(const std::string &name, HlkFm22xFaceDirection direction);
void scan_face();
void delete_face(int16_t face_id);
void delete_all_faces();
void reset();
protected:
void get_face_count_();
void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0);
void recv_command_();
void handle_note_(const std::vector<uint8_t> &data);
void handle_reply_(const std::vector<uint8_t> &data);
void set_enrolling_(bool enrolling);
HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE;
uint16_t wait_cycles_ = 0;
sensor::Sensor *face_count_sensor_{nullptr};
sensor::Sensor *status_sensor_{nullptr};
sensor::Sensor *last_face_id_sensor_{nullptr};
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
text_sensor::TextSensor *last_face_name_text_sensor_{nullptr};
text_sensor::TextSensor *version_text_sensor_{nullptr};
CallbackManager<void(uint8_t)> face_scan_invalid_callback_;
CallbackManager<void(int16_t, std::string)> face_scan_matched_callback_;
CallbackManager<void()> face_scan_unmatched_callback_;
CallbackManager<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> face_info_callback_;
CallbackManager<void(int16_t, uint8_t)> enrollment_done_callback_;
CallbackManager<void(uint8_t)> enrollment_failed_callback_;
};
class FaceScanMatchedTrigger : public Trigger<int16_t, std::string> {
public:
explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_matched_callback(
[this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); });
}
};
class FaceScanUnmatchedTrigger : public Trigger<> {
public:
explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); });
}
};
class FaceScanInvalidTrigger : public Trigger<uint8_t> {
public:
explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); });
}
};
class FaceInfoTrigger : public Trigger<int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t> {
public:
explicit FaceInfoTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_info_callback(
[this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch,
int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); });
}
};
class EnrollmentDoneTrigger : public Trigger<int16_t, uint8_t> {
public:
explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_done_callback(
[this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); });
}
};
class EnrollmentFailedTrigger : public Trigger<uint8_t> {
public:
explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); });
}
};
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
TEMPLATABLE_VALUE(std::string, name)
TEMPLATABLE_VALUE(uint8_t, direction)
void play(Ts... x) override {
auto name = this->name_.value(x...);
auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...);
this->parent_->enroll_face(name, direction);
}
};
template<typename... Ts> class DeleteAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
TEMPLATABLE_VALUE(int16_t, face_id)
void play(Ts... x) override {
auto face_id = this->face_id_.value(x...);
this->parent_->delete_face(face_id);
}
};
template<typename... Ts> class DeleteAllAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->delete_all_faces(); }
};
template<typename... Ts> class ScanAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->scan_face(); }
};
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->reset(); }
};
} // namespace esphome::hlk_fm22x

View File

@@ -1,47 +0,0 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import CONF_STATUS, ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONF_FACE_COUNT = "face_count"
CONF_LAST_FACE_ID = "last_face_id"
ICON_FACE = "mdi:face-recognition"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_FACE_COUNT): sensor.sensor_schema(
icon=ICON_FACE,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FACE_ID): sensor.sensor_schema(
icon=ICON_ACCOUNT,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
for key in [
CONF_FACE_COUNT,
CONF_STATUS,
CONF_LAST_FACE_ID,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))

View File

@@ -1,42 +0,0 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT,
ICON_RESTART,
)
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONF_LAST_FACE_NAME = "last_face_name"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
icon=ICON_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FACE_NAME): text_sensor.text_sensor_schema(
icon=ICON_ACCOUNT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
for key in [
CONF_VERSION,
CONF_LAST_FACE_NAME,
]:
if key not in config:
continue
conf = config[key]
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@@ -7,8 +7,10 @@ namespace homeassistant {
static const char *const TAG = "homeassistant.time";
void HomeassistantTime::dump_config() {
ESP_LOGCONFIG(TAG, "Home Assistant Time");
RealTimeClock::dump_config();
ESP_LOGCONFIG(TAG,
"Home Assistant Time:\n"
" Timezone: '%s'",
this->timezone_.c_str());
}
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -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);
wifi::global_wifi_component->start_connecting(sta, false);
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());

View File

@@ -4,7 +4,8 @@
#include "esphome/core/component.h"
#include "ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
template<typename... Ts> class BluetoothPasswordSetAction : public Action<Ts...> {
public:
@@ -17,4 +18,5 @@ template<typename... Ts> class BluetoothPasswordSetAction : public Action<Ts...>
LD2410Component *ld2410_comp_;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "factory_reset_button.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class FactoryResetButton : public button::Button, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class FactoryResetButton : public button::Button, public Parented<LD2410Componen
void press_action() override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "query_button.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void QueryButton::press_action() { this->parent_->read_all_info(); }
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class QueryButton : public button::Button, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class QueryButton : public button::Button, public Parented<LD2410Component> {
void press_action() override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "restart_button.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class RestartButton : public button::Button, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class RestartButton : public button::Button, public Parented<LD2410Component> {
void press_action() override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

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

View File

@@ -29,7 +29,8 @@
#include <array>
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
using namespace ld24xx;
@@ -132,4 +133,5 @@ class LD2410Component : public Component, public uart::UARTDevice {
#endif
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#include "gate_threshold_number.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {}
@@ -9,4 +10,5 @@ void GateThresholdNumber::control(float value) {
this->parent_->set_gate_threshold(this->gate_);
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class GateThresholdNumber : public number::Number, public Parented<LD2410Component> {
public:
@@ -14,4 +15,5 @@ class GateThresholdNumber : public number::Number, public Parented<LD2410Compone
void control(float value) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "light_threshold_number.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void LightThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class LightThresholdNumber : public number::Number, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class LightThresholdNumber : public number::Number, public Parented<LD2410Compon
void control(float value) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "max_distance_timeout_number.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void MaxDistanceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_max_distances_timeout();
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class MaxDistanceTimeoutNumber : public number::Number, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class MaxDistanceTimeoutNumber : public number::Number, public Parented<LD2410Co
void control(float value) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "baud_rate_select.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(this->option_at(index));
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class BaudRateSelect : public select::Select, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class BaudRateSelect : public select::Select, public Parented<LD2410Component> {
void control(size_t index) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "distance_resolution_select.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void DistanceResolutionSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_distance_resolution(this->option_at(index));
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class DistanceResolutionSelect : public select::Select, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class DistanceResolutionSelect : public select::Select, public Parented<LD2410Co
void control(size_t index) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "light_out_control_select.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void LightOutControlSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/select/select.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class LightOutControlSelect : public select::Select, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class LightOutControlSelect : public select::Select, public Parented<LD2410Compo
void control(size_t index) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -31,83 +31,35 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
),
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_FLASH,
unit_of_measurement=UNIT_PERCENT,
),
cv.Optional(CONF_LIGHT): sensor.sensor_schema(
device_class=DEVICE_CLASS_ILLUMINANCE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
@@ -121,13 +73,7 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
],
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
@@ -135,13 +81,7 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
],
icon=ICON_FLASH,
unit_of_measurement=UNIT_PERCENT,

View File

@@ -1,10 +1,12 @@
#include "bluetooth_switch.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void BluetoothSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_bluetooth(state);
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/switch/switch.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class BluetoothSwitch : public switch_::Switch, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class BluetoothSwitch : public switch_::Switch, public Parented<LD2410Component>
void write_state(bool state) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "engineering_mode_switch.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
void EngineeringModeSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_engineering_mode(state);
}
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/switch/switch.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
namespace esphome {
namespace ld2410 {
class EngineeringModeSwitch : public switch_::Switch, public Parented<LD2410Component> {
public:
@@ -13,4 +14,5 @@ class EngineeringModeSwitch : public switch_::Switch, public Parented<LD2410Comp
void write_state(bool state) override;
};
} // namespace esphome::ld2410
} // namespace ld2410
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "factory_reset_button.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class FactoryResetButton : public button::Button, public Parented<LD2412Component> {
public:
@@ -13,4 +14,5 @@ class FactoryResetButton : public button::Button, public Parented<LD2412Componen
void press_action() override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "query_button.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void QueryButton::press_action() { this->parent_->read_all_info(); }
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class QueryButton : public button::Button, public Parented<LD2412Component> {
public:
@@ -13,4 +14,5 @@ class QueryButton : public button::Button, public Parented<LD2412Component> {
void press_action() override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -1,7 +1,9 @@
#include "restart_button.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class RestartButton : public button::Button, public Parented<LD2412Component> {
public:
@@ -13,4 +14,5 @@ class RestartButton : public button::Button, public Parented<LD2412Component> {
void press_action() override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -2,8 +2,10 @@
#include "restart_button.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); }
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class StartDynamicBackgroundCorrectionButton : public button::Button, public Parented<LD2412Component> {
public:
@@ -13,4 +14,5 @@ class StartDynamicBackgroundCorrectionButton : public button::Button, public Par
void press_action() override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

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

View File

@@ -29,7 +29,8 @@
#include <array>
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
using namespace ld24xx;
@@ -136,4 +137,5 @@ class LD2412Component : public Component, public uart::UARTDevice {
#endif
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#include "gate_threshold_number.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {}
@@ -9,4 +10,5 @@ void GateThresholdNumber::control(float value) {
this->parent_->set_gate_threshold();
}
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class GateThresholdNumber : public number::Number, public Parented<LD2412Component> {
public:
@@ -14,4 +15,5 @@ class GateThresholdNumber : public number::Number, public Parented<LD2412Compone
void control(float value) override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "light_threshold_number.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void LightThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
class LightThresholdNumber : public number::Number, public Parented<LD2412Component> {
public:
@@ -13,4 +14,5 @@ class LightThresholdNumber : public number::Number, public Parented<LD2412Compon
void control(float value) override;
};
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#include "max_distance_timeout_number.h"
namespace esphome::ld2412 {
namespace esphome {
namespace ld2412 {
void MaxDistanceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_basic_config();
}
} // namespace esphome::ld2412
} // namespace ld2412
} // namespace esphome

Some files were not shown because too many files have changed in this diff Show More