Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
90e4d15fd9 [api] Release excess buffer capacity after initial sync 2025-11-05 12:14:09 -06:00
1150 changed files with 6098 additions and 13770 deletions

View File

@@ -51,79 +51,7 @@ This document provides essential context for AI models interacting with this pro
* **Naming Conventions:**
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
* **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions):
- Function, method, and variable names: `lower_snake_case`
- Class/struct/enum names: `UpperCamelCase`
- Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE`
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
* **C++ Field Visibility:**
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references.
```cpp
// Helper to find matching string in vector and return its pointer
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
for (const char *item : vec) {
if (strcmp(item, value) == 0)
return item;
}
return nullptr;
}
class ClimateDevice {
public:
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
this->custom_fan_modes_ = modes;
this->active_custom_fan_mode_ = nullptr; // Reset when modes change
}
bool set_custom_fan_mode(const char *mode) {
// Find mode in supported list and store that pointer (not the input pointer)
const char *validated_mode = vector_find(this->custom_fan_modes_, mode);
if (validated_mode != nullptr) {
this->active_custom_fan_mode_ = validated_mode;
return true;
}
return false;
}
private:
std::vector<const char *> custom_fan_modes_; // Pointers to string literals in flash
const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_
};
```
2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption.
```cpp
class Buffer {
public:
void resize(size_t new_size) {
auto new_data = std::make_unique<uint8_t[]>(new_size);
if (this->data_) {
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
}
this->data_ = std::move(new_data);
this->size_ = new_size; // Must stay in sync with data_
}
private:
std::unique_ptr<uint8_t[]> data_;
size_t size_{0}; // Must match allocated size of data_
};
```
3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip.
* **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members.
* **C++ Preprocessor Directives:**
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
* **Use `#define` only for:**
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
* **C++ Additional Conventions:**
* **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`)
* **Indentation:** Use spaces (two per indentation level), not tabs
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
* **Line length:** Wrap lines at no more than 120 characters
* **C++:** Follows the Google C++ Style Guide.
* **Component Structure:**
* **Standard Files:**
@@ -172,7 +100,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 +117,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:**
@@ -438,45 +368,3 @@ This document provides essential context for AI models interacting with this pro
* **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`.
* **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`.
* **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags.
## 8. Public API and Breaking Changes
* **Public C++ API:**
* **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal.
* **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API.
* **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters).
* **Public Python API:**
* All documented configuration options at [esphome.io](https://esphome.io) are public API.
* Python code in `esphome/core/` actively used by existing core components is considered stable API.
* Other Python code is internal unless explicitly documented for external component use.
* **Breaking Changes Policy:**
* Aim for **6-month deprecation window** when possible
* Clean breaks allowed for: signature changes, deep refactorings, resource constraints
* Must document migration path in PR description (generates release notes)
* Blog post required for core/base class changes or significant architectural changes
* Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes
* **Breaking Change Checklist:**
- [ ] Clear justification (RAM/flash savings, architectural improvement)
- [ ] Explored non-breaking alternatives
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
- [ ] Documented migration path in PR description with before/after examples
- [ ] Updated all internal usage and esphome-docs
- [ ] Tested backward compatibility during deprecation period
* **Deprecation Pattern (C++):**
```cpp
// Remove before 2026.6.0
ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0")
void old_method() { this->new_method(); }
```
* **Deprecation Pattern (Python):**
```python
# Remove before 2026.6.0
if CONF_OLD_KEY in config:
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
```

View File

@@ -7,7 +7,6 @@
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Developer breaking change (an API change that could break external components)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other

View File

@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View File

@@ -22,11 +22,11 @@ jobs:
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
steps:
- name: Checkout
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -68,7 +68,6 @@ jobs:
'bugfix',
'new-feature',
'breaking-change',
'developer-breaking-change',
'code-quality'
];
@@ -368,7 +367,6 @@ jobs:
{ pattern: /- \[x\] Bugfix \(non-breaking change which fixes an issue\)/i, label: 'bugfix' },
{ pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' },
{ pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' },
{ pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' },
{ pattern: /- \[x\] Code quality improvements to existing code or addition of tests/i, label: 'code-quality' }
];

View File

@@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.11"

View File

@@ -21,10 +21,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.11"

View File

@@ -43,9 +43,9 @@ jobs:
- "docker"
# - "lint"
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.11"
- name: Set up Docker Buildx

View File

@@ -49,7 +49,7 @@ jobs:
- name: Check out code from base repository
if: steps.pr.outputs.skip != 'true'
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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,13 +36,13 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -70,7 +70,7 @@ jobs:
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Fetch enough history to find the merge base
fetch-depth: 2
@@ -237,10 +237,10 @@ jobs:
if: needs.determine-jobs.outputs.integration-tests == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.13"
- name: Restore Python virtual environment
@@ -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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
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@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
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@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Get tag
id: tag
# yamllint disable rule:line-length
@@ -60,9 +60,9 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.x"
- name: Build
@@ -92,9 +92,9 @@ jobs:
os: "ubuntu-24.04-arm"
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.11"
@@ -168,7 +168,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Download digests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0

View File

@@ -13,16 +13,16 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Checkout Home Assistant
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: home-assistant/core
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: 3.13
@@ -41,7 +41,7 @@ jobs:
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>

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.3
hooks:
# Run the linter.
- id: ruff

View File

@@ -72,7 +72,6 @@ esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/ble_nus/* @tomaszduda23
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
esphome/components/bm8563/* @abmantis
esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth
@@ -203,12 +202,10 @@ esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/hc8/* @omartijn
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
@@ -293,7 +290,6 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner
esphome/components/mcp3221/* @philippderdiedas
esphome/components/mcp4461/* @p1ngb4ck
esphome/components/mcp4728/* @berfenger
esphome/components/mcp47a1/* @jesserockz
@@ -398,7 +394,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
@@ -462,7 +457,6 @@ esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155
esphome/components/statsd/* @Links2004
esphome/components/stts22h/* @B48D81EFCC
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931
@@ -484,7 +478,6 @@ esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse
esphome/components/thermopro_ble/* @sittner
esphome/components/thermostat/* @kbx81
esphome/components/time/* @esphome/core
esphome/components/tinyusb/* @kbx81

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

@@ -1319,7 +1319,7 @@ def parse_args(argv):
"clean-all", help="Clean all build and platform files."
)
parser_clean_all.add_argument(
"configuration", help="Your YAML file or configuration directory.", nargs="*"
"configuration", help="Your YAML configuration directory.", nargs="*"
)
parser_dashboard = subparsers.add_parser(

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

@@ -87,7 +87,7 @@ void AbsoluteHumidityComponent::loop() {
break;
default:
this->publish_state(NAN);
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
this->status_set_error("Invalid saturation vapor pressure equation selection!");
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -83,7 +83,7 @@ void AHT10Component::setup() {
void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0;
this->status_set_error(LOG_STR("Reading timed out"));
this->status_set_error("Reading timed out");
return;
}
this->read_count_++;

View File

@@ -1,9 +1,7 @@
#include "alarm_control_panel.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include <utility>
#include "alarm_control_panel.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -36,9 +34,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
this->state_callback_.call();
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this);
#endif
if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {

View File

@@ -56,13 +56,13 @@ bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str());
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response too short", this->parent_->address_str());
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str(),
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
@@ -77,11 +77,11 @@ void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str());
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str());
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
@@ -100,7 +100,7 @@ void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc
if (param->open.status == ESP_GATT_OK) {
this->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str());
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
}
break;
}
@@ -132,7 +132,7 @@ void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str());
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
@@ -164,12 +164,12 @@ void Alpha3::send_request_(uint8_t *request, size_t len) {
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
void Alpha3::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str());
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}

View File

@@ -44,9 +44,11 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->parent_->address_str());
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
this->parent_->address_str().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->parent_->address_str());
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
this->parent_->address_str().c_str());
}
break;
}
@@ -80,7 +82,8 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
this->char_handle_, packet->length, packet->data,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
}
this->current_sensor_ = 0;
@@ -94,7 +97,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
void Am43::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str());
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->current_sensor_ == 0) {
@@ -104,7 +107,7 @@ void Am43::update() {
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
}
this->current_sensor_++;

View File

@@ -42,7 +42,7 @@ void Anova::control(const ClimateCall &call) {
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
}
if (call.get_target_temperature().has_value()) {
@@ -51,7 +51,7 @@ void Anova::control(const ClimateCall &call) {
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
}
}
@@ -124,7 +124,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
}
}
@@ -149,7 +150,7 @@ void Anova::update() {
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
this->current_request_++;
}

View File

@@ -85,7 +85,6 @@ CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
CONF_LISTEN_BACKLOG = "listen_backlog"
CONF_MAX_SEND_QUEUE = "max_send_queue"
CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only"
def validate_encryption_key(value):
@@ -228,7 +227,6 @@ CONFIG_SCHEMA = cv.All(
esp32=8, # More RAM, can buffer more
rp2040=5, # Limited RAM
bk72xx=8, # Moderate RAM
nrf52=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
host=16, # Abundant resources
ln882x=8, # Moderate RAM
@@ -246,9 +244,6 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Track controller registration for StaticVector sizing
CORE.register_controller()
cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_PASSWORD")
@@ -261,9 +256,9 @@ async def to_code(config):
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
# Set USE_API_USER_DEFINED_ACTIONS if any services are enabled
# Set USE_API_SERVICES if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_USER_DEFINED_ACTIONS")
cg.add_define("USE_API_SERVICES")
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
if config[CONF_CUSTOM_SERVICES]:
@@ -538,24 +533,9 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
return var
API_CONNECTED_CONDITION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(APIServer),
cv.Optional(CONF_STATE_SUBSCRIPTION_ONLY, default=False): cv.templatable(
cv.boolean
),
}
)
@automation.register_condition(
"api.connected", APIConnectedCondition, API_CONNECTED_CONDITION_SCHEMA
)
@automation.register_condition("api.connected", APIConnectedCondition, {})
async def api_connected_to_code(config, condition_id, template_arg, args):
var = cg.new_Pvariable(condition_id, template_arg)
templ = await cg.templatable(config[CONF_STATE_SUBSCRIPTION_ONLY], args, cg.bool_)
cg.add(var.set_state_subscription_only(templ))
return var
return cg.new_Pvariable(condition_id, template_arg)
def FILTER_SOURCE_FILES() -> list[str]:

View File

@@ -518,7 +518,7 @@ message ListEntitiesLightResponse {
bool legacy_supports_color_temperature = 8 [deprecated=true];
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11 [(container_pointer_no_template) = "FixedVector<const char *>"];
repeated string effects = 11;
bool disabled_by_default = 13;
string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 15;
@@ -855,21 +855,21 @@ enum ServiceArgType {
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
}
message ListEntitiesServicesArgument {
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
option (ifdef) = "USE_API_SERVICES";
string name = 1;
ServiceArgType type = 2;
}
message ListEntitiesServicesResponse {
option (id) = 41;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
option (ifdef) = "USE_API_SERVICES";
string name = 1;
fixed32 key = 2;
repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true];
}
message ExecuteServiceArgument {
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
option (ifdef) = "USE_API_SERVICES";
bool bool_ = 1;
int32 legacy_int = 2;
float float_ = 3;
@@ -885,7 +885,7 @@ message ExecuteServiceRequest {
option (id) = 42;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
option (ifdef) = "USE_API_SERVICES";
fixed32 key = 1;
repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true];
@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
repeated string event_types = 9;
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
}
message EventResponse {

View File

@@ -90,8 +90,8 @@ static const int CAMERA_STOP_STREAM = 5000;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) {
auto noise_ctx = parent->get_noise_ctx();
if (noise_ctx->has_psk()) {
this->helper_ =
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
} else {
@@ -193,6 +193,11 @@ void APIConnection::loop() {
if (!this->deferred_batch_.empty()) {
this->process_batch_();
}
// Release excess capacity from initial entity flood
// deferred_batch_ grew up to MAX_INITIAL_PER_BATCH (24) items, now shrink to free up to ~384 bytes
this->deferred_batch_.items.shrink_to_fit();
// shared_write_buffer_ grew up to ~1.5KB during initial state, now shrink to free up to ~1.4KB
this->parent_->get_shared_buffer_ref().shrink_to_fit();
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
}
@@ -476,24 +481,19 @@ 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();
msg.max_mireds = traits.get_max_mireds();
}
FixedVector<const char *> effects_list;
if (light->supports_effects()) {
auto &light_effects = light->get_effects();
effects_list.init(light_effects.size() + 1);
effects_list.push_back("None");
for (auto *effect : light_effects) {
effects_list.push_back(effect->get_name());
msg.effects.emplace_back("None");
for (auto *effect : light->get_effects()) {
msg.effects.emplace_back(effect->get_name());
}
}
msg.effects = &effects_list;
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1299,11 +1299,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));
@@ -1315,7 +1315,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.set_device_class(event->get_device_class_ref());
msg.event_types = &event->get_event_types();
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1455,11 +1456,8 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_AREAS
resp.set_suggested_area(StringRef(App.get_area()));
#endif
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char mac_address[18];
uint8_t mac[6];
get_mac_address_raw(mac);
format_mac_addr_upper(mac, mac_address);
// mac_address must store temporary string - will be valid during send_message call
std::string mac_address = get_mac_address_pretty();
resp.set_mac_address(StringRef(mac_address));
resp.set_esphome_version(ESPHOME_VERSION_REF);
@@ -1475,8 +1473,6 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
#elif defined(USE_LN882X)
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
#elif defined(USE_NRF52)
static constexpr auto MANUFACTURER = StringRef::from_lit("Nordic Semiconductor");
#elif defined(USE_RTL87XX)
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
#elif defined(USE_HOST)
@@ -1500,9 +1496,8 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#endif
#ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
char bluetooth_mac[18];
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
// bt_mac must store temporary string - will be valid during send_message call
std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
#endif
#ifdef USE_VOICE_ASSISTANT
@@ -1549,7 +1544,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
}
}
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
for (auto *service : this->parent_->get_user_services()) {
@@ -1659,7 +1654,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;
}
@@ -1829,7 +1826,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_();
@@ -1842,10 +1839,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
@@ -221,7 +221,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE
@@ -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

@@ -84,7 +84,9 @@ class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: socket_(std::move(socket)), client_info_(client_info) {}
: socket_owned_(std::move(socket)), client_info_(client_info) {
socket_ = socket_owned_.get();
}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
@@ -147,8 +149,9 @@ class APIFrameHelper {
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state);
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
std::unique_ptr<socket::Socket> socket_;
// Pointers first (4 bytes each)
socket::Socket *socket_{nullptr};
std::unique_ptr<socket::Socket> socket_owned_;
// Common state enum for all frame helpers
// Note: Not all states are used by all implementations

View File

@@ -239,13 +239,12 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (state_ == State::SERVER_HELLO) {
// send server hello
constexpr size_t mac_len = 13; // 12 hex chars + null terminator
const std::string &name = App.get_name();
char mac[mac_len];
get_mac_address_into_buffer(mac);
const std::string &mac = get_mac_address();
// Calculate positions and sizes
size_t name_len = name.size() + 1; // including null terminator
size_t mac_len = mac.size() + 1; // including null terminator
size_t name_offset = 1;
size_t mac_offset = name_offset + name_len;
size_t total_size = 1 + name_len + mac_len;
@@ -258,7 +257,7 @@ APIError APINoiseFrameHelper::state_action_() {
// node name, terminated by null byte
std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
// node mac, terminated by null byte
std::memcpy(msg.get() + mac_offset, mac, mac_len);
std::memcpy(msg.get() + mac_offset, mac.c_str(), mac_len);
aerr = write_frame_(msg.get(), total_size);
if (aerr != APIError::OK)
@@ -528,7 +527,7 @@ APIError APINoiseFrameHelper::init_handshake_() {
if (aerr != APIError::OK)
return aerr;
const auto &psk = this->ctx_.get_psk();
const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
APIError::HANDSHAKESTATE_SETUP_FAILED);

View File

@@ -9,8 +9,9 @@ namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(std::move(ctx)) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)
@@ -40,8 +41,8 @@ class APINoiseFrameHelper final : public APIFrameHelper {
NoiseCipherState *send_cipher_{nullptr};
NoiseCipherState *recv_cipher_{nullptr};
// Reference to noise context (4 bytes on 32-bit)
APINoiseContext &ctx_;
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
std::shared_ptr<APINoiseContext> ctx_;
// Vector (12 bytes on 32-bit)
std::vector<uint8_t> prologue_;

View File

@@ -476,8 +476,8 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_float(9, this->min_mireds);
buffer.encode_float(10, this->max_mireds);
for (const char *it : *this->effects) {
buffer.encode_string(11, it, strlen(it), true);
for (auto &it : this->effects) {
buffer.encode_string(11, it, true);
}
buffer.encode_bool(13, this->disabled_by_default);
#ifdef USE_ENTITY_ICON
@@ -499,9 +499,9 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const {
}
size.add_float(1, this->min_mireds);
size.add_float(1, this->max_mireds);
if (!this->effects->empty()) {
for (const char *it : *this->effects) {
size.add_length_force(1, strlen(it));
if (!this->effects.empty()) {
for (const auto &it : this->effects) {
size.add_length_force(1, it.size());
}
}
size.add_bool(1, this->disabled_by_default);
@@ -995,7 +995,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
return true;
}
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_);
buffer.encode_uint32(2, static_cast<uint32_t>(this->type));
@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
buffer.encode_string(8, this->device_class_ref_);
for (const char *it : *this->event_types) {
buffer.encode_string(9, it, strlen(it), true);
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
}
#ifdef USE_DEVICES
buffer.encode_uint32(10, this->device_id);
@@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->disabled_by_default);
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
size.add_length(1, this->device_class_ref_.size());
if (!this->event_types->empty()) {
for (const char *it : *this->event_types) {
size.add_length_force(1, strlen(it));
if (!this->event_types.empty()) {
for (const auto &it : this->event_types) {
size.add_length_force(1, it.size());
}
}
#ifdef USE_DEVICES

View File

@@ -63,7 +63,7 @@ enum LogLevel : uint32_t {
LOG_LEVEL_VERBOSE = 6,
LOG_LEVEL_VERY_VERBOSE = 7,
};
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
SERVICE_ARG_TYPE_INT = 1,
@@ -793,7 +793,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
const light::ColorModeMask *supported_color_modes{};
float min_mireds{0.0f};
float max_mireds{0.0f};
const FixedVector<const char *> *effects{};
std::vector<std::string> effects{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1239,7 +1239,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
class ListEntitiesServicesArgument final : public ProtoMessage {
public:
StringRef name_ref_{};
@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
const FixedVector<const char *> *event_types{};
std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

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);
}
@@ -206,7 +206,7 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
return "UNKNOWN";
}
}
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
switch (value) {
case enums::SERVICE_ARG_TYPE_BOOL:
@@ -924,7 +924,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
}
dump_field(out, "min_mireds", this->min_mireds);
dump_field(out, "max_mireds", this->max_mireds);
for (const auto &it : *this->effects) {
for (const auto &it : this->effects) {
dump_field(out, "effects", it, 4);
}
dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1177,7 +1177,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("\n");
}
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
dump_field(out, "name", this->name_ref_);
@@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
for (const auto &it : *this->event_types) {
for (const auto &it : this->event_types) {
dump_field(out, "event_types", it, 4);
}
#ifdef USE_DEVICES

View File

@@ -193,7 +193,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
case ExecuteServiceRequest::MESSAGE_TYPE: {
ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size);
@@ -670,7 +670,7 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
this->subscribe_home_assistant_states(msg);
}
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
#endif
#ifdef USE_API_NOISE

View File

@@ -79,7 +79,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
@@ -239,7 +239,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE
@@ -368,7 +368,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE

View File

@@ -5,7 +5,6 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@@ -35,7 +34,7 @@ APIServer::APIServer() {
}
void APIServer::setup() {
ControllerRegistry::register_controller(this);
this->setup_controller();
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
@@ -227,8 +226,8 @@ void APIServer::dump_config() {
" Max connections: %u",
network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk()));
if (!this->noise_ctx_.has_psk()) {
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Supports encryption: YES");
}
#else
@@ -270,7 +269,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for controller update dispatch
// Macro for entities without extra parameters
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
@@ -279,6 +278,15 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
c->send_##entity_name##_state(obj); \
}
// Macro for entities with extra parameters (but parameters not used in send)
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
#ifdef USE_BINARY_SENSOR
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
#endif
@@ -296,15 +304,15 @@ API_DISPATCH_UPDATE(light::LightState, light)
#endif
#ifdef USE_SENSOR
API_DISPATCH_UPDATE(sensor::Sensor, sensor)
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
#endif
#ifdef USE_SWITCH
API_DISPATCH_UPDATE(switch_::Switch, switch)
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
#endif
#ifdef USE_TEXT_SENSOR
API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor)
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
#endif
#ifdef USE_CLIMATE
@@ -312,7 +320,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate)
#endif
#ifdef USE_NUMBER
API_DISPATCH_UPDATE(number::Number, number)
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
#endif
#ifdef USE_DATETIME_DATE
@@ -328,11 +336,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
#endif
#ifdef USE_TEXT
API_DISPATCH_UPDATE(text::Text, text)
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
#endif
#ifdef USE_SELECT
API_DISPATCH_UPDATE(select::Select, select)
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
#endif
#ifdef USE_LOCK
@@ -348,13 +356,12 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
#endif
#ifdef USE_EVENT
// Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type()
void APIServer::on_event(event::Event *obj) {
// Event is a special case - it's the only entity that passes extra parameters to the send method
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_event(obj, obj->get_last_event_type());
c->send_event(obj, event_type);
}
#endif
@@ -493,7 +500,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
ESP_LOGW(TAG, "Key set in YAML");
return false;
#else
auto &old_psk = this->noise_ctx_.get_psk();
auto &old_psk = this->noise_ctx_->get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old");
return true;
@@ -528,18 +535,7 @@ void APIServer::request_time() {
}
#endif
bool APIServer::is_connected(bool state_subscription_only) const {
if (!state_subscription_only) {
return !this->clients_.empty();
}
for (const auto &client : this->clients_) {
if (client->flags_.state_subscription) {
return true;
}
}
return false;
}
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
this->shutting_down_ = true;

View File

@@ -12,7 +12,7 @@
#include "esphome/core/log.h"
#include "list_entities.h"
#include "subscribe_state.h"
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
#include "user_services.h"
#endif
@@ -54,8 +54,8 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
bool clear_noise_psk(bool make_active = true);
void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); }
APINoiseContext &get_noise_ctx() { return this->noise_ctx_; }
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
#endif // USE_API_NOISE
void handle_disconnect(APIConnection *conn);
@@ -72,19 +72,19 @@ class APIServer : public Component, public Controller {
void on_light_update(light::LightState *obj) override;
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj) override;
void on_sensor_update(sensor::Sensor *obj, float state) override;
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj) override;
void on_switch_update(switch_::Switch *obj, bool state) override;
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj) override;
void on_number_update(number::Number *obj, float state) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
@@ -96,10 +96,10 @@ class APIServer : public Component, public Controller {
void on_datetime_update(datetime::DateTimeEntity *obj) override;
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj) override;
void on_text_update(text::Text *obj, const std::string &state) override;
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj) override;
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
@@ -124,7 +124,7 @@ class APIServer : public Component, public Controller {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
this->user_services_.assign(services);
}
@@ -141,7 +141,7 @@ class APIServer : public Component, public Controller {
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj) override;
void on_event(event::Event *obj, const std::string &event_type) override;
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
@@ -150,7 +150,7 @@ class APIServer : public Component, public Controller {
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
#endif
bool is_connected(bool state_subscription_only = false) const;
bool is_connected() const;
#ifdef USE_API_HOMEASSISTANT_STATES
struct HomeAssistantStateSubscription {
@@ -166,7 +166,7 @@ class APIServer : public Component, public Controller {
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#endif
@@ -206,7 +206,7 @@ class APIServer : public Component, public Controller {
#ifdef USE_API_HOMEASSISTANT_STATES
std::vector<HomeAssistantStateSubscription> state_subs_;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
std::vector<UserServiceDescriptor *> user_services_;
#endif
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
@@ -228,7 +228,7 @@ class APIServer : public Component, public Controller {
// 7 bytes used, 1 byte padding
#ifdef USE_API_NOISE
APINoiseContext noise_ctx_;
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
ESPPreferenceObject noise_pref_;
#endif // USE_API_NOISE
};
@@ -236,11 +236,8 @@ class APIServer : public Component, public Controller {
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
TEMPLATABLE_VALUE(bool, state_subscription_only)
public:
bool check(const Ts &...x) override {
return global_api_server->is_connected(this->state_subscription_only_.value(x...));
}
bool check(const Ts &...x) override { return global_api_server->is_connected(); }
};
} // namespace esphome::api

View File

@@ -3,17 +3,17 @@
#include <map>
#include "api_server.h"
#ifdef USE_API
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
#include "user_services.h"
#endif
namespace esphome::api {
#ifdef USE_API_USER_DEFINED_ACTIONS
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
void (T::*callback)(Ts...))
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
protected:
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
@@ -21,7 +21,7 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
T *obj_;
void (T::*callback_)(Ts...);
};
#endif // USE_API_USER_DEFINED_ACTIONS
#endif // USE_API_SERVICES
class CustomAPIDevice {
public:
@@ -49,7 +49,7 @@ class CustomAPIDevice {
* @param name The name of the service to register.
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
@@ -90,7 +90,7 @@ class CustomAPIDevice {
* @param callback The member function to call when the service is triggered.
* @param name The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT

View File

@@ -82,7 +82,7 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response();
return this->client_->send_message(resp, ListEntitiesServicesResponse::MESSAGE_TYPE);

View File

@@ -43,7 +43,7 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override;
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
bool on_service(UserServiceDescriptor *service) override;
#endif
#ifdef USE_CAMERA

View File

@@ -7,7 +7,7 @@
#include "esphome/core/automation.h"
#include "api_pb2.h"
#ifdef USE_API_USER_DEFINED_ACTIONS
#ifdef USE_API_SERVICES
namespace esphome::api {
class UserServiceDescriptor {
@@ -23,58 +23,11 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
template<typename T> enums::ServiceArgType to_service_arg_type();
// Base class for YAML-defined services (most common case)
// Stores only pointers to string literals in flash - no heap allocation
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public:
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
this->key_ = fnv1_hash(name);
}
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.set_name(StringRef(this->name_));
msg.key = this->key_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
msg.args.init(sizeof...(Ts));
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.set_name(StringRef(this->arg_names_[i]));
}
return msg;
}
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, size_t... S>
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
// Pointers to string literals in flash - no heap allocation
const char *name_;
std::array<const char *, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
};
// Separate class for custom_api_device services (rare case)
// Stores copies of runtime-generated names
template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor {
public:
UserServiceDynamic(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_.c_str());
this->key_ = fnv1_hash(this->name_);
}
ListEntitiesServicesResponse encode_list_service_response() override {
@@ -94,29 +47,26 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != sizeof...(Ts))
if (req.args.size() != this->arg_names_.size())
return false;
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, size_t... S>
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
// Heap-allocated strings for runtime-generated names
std::string name_;
std::array<std::string, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
std::array<std::string, sizeof...(Ts)> arg_names_;
};
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
public:
// Constructor for static names (YAML-defined services - used by code generator)
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: UserServiceBase<Ts...>(name, arg_names) {}
protected:
@@ -124,4 +74,4 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
};
} // namespace esphome::api
#endif // USE_API_USER_DEFINED_ACTIONS
#endif // USE_API_SERVICES

View File

@@ -23,7 +23,7 @@ void BH1900NUXSensor::setup() {
i2c::ErrorCode result_code =
this->write_register(SOFT_RESET_REG, &SOFT_RESET_PAYLOAD, 1); // Software Reset to check communication
if (result_code != i2c::ERROR_OK) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
}

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

@@ -1,9 +1,9 @@
#include "binary_sensor.h"
#include "esphome/core/defines.h"
#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";
@@ -37,9 +37,6 @@ void BinarySensor::send_state_internal(bool new_state) {
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
if (this->set_state_(new_state)) {
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state));
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_binary_sensor_update(this);
#endif
}
}
@@ -61,4 +58,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

@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() {
float value = 0.0f;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
if (!this->pref_.load(&value)) {
value = 0.0f;
}

View File

@@ -15,7 +15,6 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VALUE,
)
from esphome.core import ID
AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
@@ -199,12 +198,7 @@ async def ble_write_to_code(config, action_id, template_arg, args):
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_value_template(templ))
else:
# Generate static array in flash to avoid RAM copy
if isinstance(value, bytes):
value = list(value)
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*value))
cg.add(var.set_value_simple(arr, len(value)))
cg.add(var.set_value_simple(value))
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(

View File

@@ -96,8 +96,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
BLEClientWriteAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
this->construct_simple_value_();
}
~BLEClientWriteAction() { this->destroy_simple_value_(); }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
@@ -107,14 +110,17 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
this->value_.func = func;
this->len_ = -1; // Sentinel value indicates template mode
this->destroy_simple_value_();
this->value_.template_func = func;
this->has_simple_value_ = false;
}
// Store pointer to static data in flash (no RAM copy)
void set_value_simple(const uint8_t *data, size_t len) {
this->value_.data = data;
this->len_ = len; // Length >= 0 indicates static mode
void set_value_simple(const std::vector<uint8_t> &value) {
if (!this->has_simple_value_) {
this->construct_simple_value_();
}
this->value_.simple = value;
this->has_simple_value_ = true;
}
void play(const Ts &...x) override {}
@@ -122,19 +128,9 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void play_complex(const Ts &...x) override {
this->num_running_++;
this->var_ = std::make_tuple(x...);
bool result;
if (this->len_ >= 0) {
// Static mode: write directly from flash pointer
result = this->write(this->value_.data, this->len_);
} else {
// Template mode: call function and write the vector
std::vector<uint8_t> value = this->value_.func(x...);
result = this->write(value);
}
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!result)
if (!write(value))
this->play_next_(x...);
}
@@ -147,15 +143,15 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
* errors.
*/
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
bool write(const uint8_t *data, size_t len) {
bool write(const std::vector<uint8_t> &value) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) {
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
return false;
@@ -163,8 +159,6 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
return true;
}
bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
switch (event) {
@@ -198,7 +192,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
}
this->node_state = espbt::ClientState::ESTABLISHED;
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
ble_client_->address_str());
ble_client_->address_str().c_str());
break;
}
default:
@@ -207,11 +201,21 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
}
private:
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
void destroy_simple_value_() {
if (this->has_simple_value_) {
this->value_.simple.~vector();
}
}
BLEClient *ble_client_;
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
bool has_simple_value_ = true;
union Value {
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
const uint8_t *data; // Pointer to static data in flash
std::vector<uint8_t> simple;
std::vector<uint8_t> (*template_func)(Ts...);
Value() {} // trivial constructor
~Value() {} // trivial destructor - we manage lifetime via discriminator
} value_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;

View File

@@ -39,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) {
return;
this->enabled = enabled;
if (!enabled) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str());
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
this->disconnect();
}
}

View File

@@ -14,7 +14,7 @@ void BLEBinaryOutput::dump_config() {
" MAC address : %s\n"
" Service UUID : %s\n"
" Characteristic UUID: %s",
this->parent_->address_str(), this->service_uuid_.to_string().c_str(),
this->parent_->address_str().c_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
@@ -44,7 +44,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
}
this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
this->parent()->address_str());
this->parent()->address_str().c_str());
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}

View File

@@ -19,7 +19,7 @@ void BLEClientRSSISensor::loop() {
void BLEClientRSSISensor::dump_config() {
LOG_SENSOR("", "BLE Client RSSI Sensor", this);
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str());
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
LOG_UPDATE_INTERVAL(this);
}
@@ -69,10 +69,10 @@ void BLEClientRSSISensor::update() {
this->get_rssi_();
}
void BLEClientRSSISensor::get_rssi_() {
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str());
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
if (status != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str(), status);
ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
this->status_set_warning();
this->publish_state(NAN);
}

View File

@@ -25,7 +25,7 @@ void BLESensor::dump_config() {
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->parent()->address_str().c_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -28,7 +28,7 @@ void BLETextSensor::dump_config() {
" Characteristic UUID: %s\n"
" Descriptor UUID : %s\n"
" Notifications : %s",
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
this->parent()->address_str().c_str(), this->service_uuid_.to_string().c_str(),
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
LOG_UPDATE_INTERVAL(this);
}

View File

@@ -196,8 +196,8 @@ void BluetoothConnection::send_service_for_discovery_() {
if (service_status != ESP_GATT_OK || service_count == 0) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d",
this->connection_index_, this->address_str(), service_status != ESP_GATT_OK ? "error" : "missing",
service_status, service_count, this->send_service_);
this->connection_index_, this->address_str().c_str(),
service_status != ESP_GATT_OK ? "error" : "missing", service_status, service_count, this->send_service_);
this->send_service_ = DONE_SENDING_SERVICES;
return;
}
@@ -312,13 +312,13 @@ void BluetoothConnection::send_service_for_discovery_() {
if (resp.services.size() > 1) {
resp.services.pop_back();
ESP_LOGD(TAG, "[%d] [%s] Service %d would exceed limit (current: %d + service: %d > %d), sending current batch",
this->connection_index_, this->address_str(), this->send_service_, current_size, service_size,
this->connection_index_, this->address_str().c_str(), this->send_service_, current_size, service_size,
MAX_PACKET_SIZE);
// Don't increment send_service_ - we'll retry this service in next batch
} else {
// This single service is too large, but we have to send it anyway
ESP_LOGV(TAG, "[%d] [%s] Service %d is too large (%d bytes) but sending anyway", this->connection_index_,
this->address_str(), this->send_service_, service_size);
this->address_str().c_str(), this->send_service_, service_size);
// Increment so we don't get stuck
this->send_service_++;
}
@@ -337,20 +337,21 @@ void BluetoothConnection::send_service_for_discovery_() {
}
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str(), operation, status);
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation,
status);
}
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str(), operation, err);
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err);
}
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str(), action,
type);
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(),
action, type);
}
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str(),
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(),
operation, handle, status);
}
@@ -371,14 +372,14 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
case ESP_GATTC_DISCONNECT_EVT: {
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
// This prevents race condition where we mark slot as free before controller cleanup is complete
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_,
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
param->disconnect.reason);
// Send disconnection notification but don't free the slot yet
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
break;
}
case ESP_GATTC_CLOSE_EVT: {
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_,
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
param->close.reason);
// Now the GATT connection is fully closed and controller resources are freed
// Safe to mark the connection slot as available
@@ -462,7 +463,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
break;
}
case ESP_GATTC_NOTIFY_EVT: {
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_,
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
param->notify.handle);
api::BluetoothGATTNotifyDataResponse resp;
resp.address = this->address_;
@@ -501,7 +502,8 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_, handle);
ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
handle);
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
@@ -513,7 +515,8 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8
this->log_gatt_not_connected_("write", "characteristic");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_, handle);
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
@@ -529,7 +532,8 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
this->log_gatt_not_connected_("read", "descriptor");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_, handle);
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
handle);
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
@@ -540,7 +544,8 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *
this->log_gatt_not_connected_("write", "descriptor");
return ESP_GATT_NOT_CONNECTED;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_, handle);
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
handle);
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
@@ -559,13 +564,13 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
if (enable) {
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
this->address_str_, handle);
this->address_str_.c_str(), handle);
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
}
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
this->address_str_, handle);
this->address_str_.c_str(), handle);
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
}

View File

@@ -47,11 +47,12 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
connection->address_str(), espbt::client_state_to_string(state));
connection->address_str().c_str(), espbt::client_state_to_string(state));
}
void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str(), message);
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(),
message);
}
void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
@@ -185,7 +186,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
}
if (!msg.has_address_type) {
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
connection->address_str());
connection->address_str().c_str());
this->send_device_connection(msg.address, false);
return;
}
@@ -198,7 +199,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
} else if (connection->state() == espbt::ClientState::CONNECTING) {
if (connection->disconnect_pending()) {
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
connection->get_connection_index(), connection->address_str());
connection->get_connection_index(), connection->address_str().c_str());
connection->cancel_pending_disconnect();
return;
}
@@ -338,7 +339,7 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
return;
}
if (!connection->service_count_) {
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str());
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
this->send_gatt_services_done(msg.address);
return;
}

View File

@@ -130,13 +130,11 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
return flags;
}
void get_bluetooth_mac_address_pretty(std::span<char, 18> output) {
std::string get_bluetooth_mac_address_pretty() {
const uint8_t *mac = esp_bt_dev_get_address();
if (mac != nullptr) {
format_mac_addr_upper(mac, output.data());
} else {
output[0] = '\0';
}
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
}
protected:

View File

@@ -1 +0,0 @@
CODEOWNERS = ["@abmantis"]

View File

@@ -1,198 +0,0 @@
#include "bm8563.h"
#include "esphome/core/log.h"
namespace esphome::bm8563 {
static const char *const TAG = "bm8563";
static constexpr uint8_t CONTROL_STATUS_1_REG = 0x00;
static constexpr uint8_t CONTROL_STATUS_2_REG = 0x01;
static constexpr uint8_t TIME_FIRST_REG = 0x02; // Time uses reg 2, 3, 4
static constexpr uint8_t DATE_FIRST_REG = 0x05; // Date uses reg 5, 6, 7, 8
static constexpr uint8_t TIMER_CONTROL_REG = 0x0E;
static constexpr uint8_t TIMER_VALUE_REG = 0x0F;
static constexpr uint8_t CLOCK_1_HZ = 0x82;
static constexpr uint8_t CLOCK_1_60_HZ = 0x83;
// Maximum duration: 255 minutes (at 1/60 Hz) = 15300 seconds
static constexpr uint32_t MAX_TIMER_DURATION_S = 255 * 60;
void BM8563::setup() {
if (!this->write_byte_16(CONTROL_STATUS_1_REG, 0)) {
this->mark_failed();
return;
}
}
void BM8563::update() { this->read_time(); }
void BM8563::dump_config() {
ESP_LOGCONFIG(TAG, "BM8563:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
}
void BM8563::start_timer(uint32_t duration_s) {
this->clear_irq_();
this->set_timer_irq_(duration_s);
}
void BM8563::write_time() {
auto now = time::RealTimeClock::utcnow();
if (!now.is_valid()) {
ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
return;
}
ESP_LOGD(TAG, "Writing time: %i-%i-%i %i, %i:%i:%i", now.year, now.month, now.day_of_month, now.day_of_week, now.hour,
now.minute, now.second);
this->set_time_(now);
this->set_date_(now);
}
void BM8563::read_time() {
ESPTime rtc_time;
this->get_time_(rtc_time);
this->get_date_(rtc_time);
rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid
ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
return;
}
time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
}
uint8_t BM8563::bcd2_to_byte_(uint8_t value) {
const uint8_t tmp = ((value & 0xF0) >> 0x4) * 10;
return tmp + (value & 0x0F);
}
uint8_t BM8563::byte_to_bcd2_(uint8_t value) {
const uint8_t bcdhigh = value / 10;
value -= bcdhigh * 10;
return (bcdhigh << 4) | value;
}
void BM8563::get_time_(ESPTime &time) {
uint8_t buf[3] = {0};
this->read_register(TIME_FIRST_REG, buf, 3);
time.second = this->bcd2_to_byte_(buf[0] & 0x7f);
time.minute = this->bcd2_to_byte_(buf[1] & 0x7f);
time.hour = this->bcd2_to_byte_(buf[2] & 0x3f);
}
void BM8563::set_time_(const ESPTime &time) {
uint8_t buf[3] = {this->byte_to_bcd2_(time.second), this->byte_to_bcd2_(time.minute), this->byte_to_bcd2_(time.hour)};
this->write_register_(TIME_FIRST_REG, buf, 3);
}
void BM8563::get_date_(ESPTime &time) {
uint8_t buf[4] = {0};
this->read_register(DATE_FIRST_REG, buf, sizeof(buf));
time.day_of_month = this->bcd2_to_byte_(buf[0] & 0x3f);
time.day_of_week = this->bcd2_to_byte_(buf[1] & 0x07);
time.month = this->bcd2_to_byte_(buf[2] & 0x1f);
uint8_t year_byte = this->bcd2_to_byte_(buf[3] & 0xff);
if (buf[2] & 0x80) {
time.year = 1900 + year_byte;
} else {
time.year = 2000 + year_byte;
}
}
void BM8563::set_date_(const ESPTime &time) {
uint8_t buf[4] = {
this->byte_to_bcd2_(time.day_of_month),
this->byte_to_bcd2_(time.day_of_week),
this->byte_to_bcd2_(time.month),
this->byte_to_bcd2_(time.year % 100),
};
if (time.year < 2000) {
buf[2] = buf[2] | 0x80;
}
this->write_register_(DATE_FIRST_REG, buf, 4);
}
void BM8563::write_byte_(uint8_t reg, uint8_t value) {
if (!this->write_byte(reg, value)) {
ESP_LOGE(TAG, "Failed to write byte 0x%02X with value 0x%02X", reg, value);
}
}
void BM8563::write_register_(uint8_t reg, const uint8_t *data, size_t len) {
if (auto error = this->write_register(reg, data, len); error != i2c::ErrorCode::NO_ERROR) {
ESP_LOGE(TAG, "Failed to write register 0x%02X with %zu bytes", reg, len);
}
}
optional<uint8_t> BM8563::read_register_(uint8_t reg) {
uint8_t data;
if (auto error = this->read_register(reg, &data, 1); error != i2c::ErrorCode::NO_ERROR) {
ESP_LOGE(TAG, "Failed to read register 0x%02X", reg);
return {};
}
return data;
}
void BM8563::set_timer_irq_(uint32_t duration_s) {
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
if (duration_s > MAX_TIMER_DURATION_S) {
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
return;
}
if (duration_s > 255) {
uint8_t duration_minutes = duration_s / 60;
this->write_byte_(TIMER_VALUE_REG, duration_minutes);
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_60_HZ);
} else {
this->write_byte_(TIMER_VALUE_REG, duration_s);
this->write_byte_(TIMER_CONTROL_REG, CLOCK_1_HZ);
}
auto maybe_ctrl_status_2 = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_ctrl_status_2.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t ctrl_status_2_reg_value = maybe_ctrl_status_2.value();
ctrl_status_2_reg_value |= (1 << 0);
ctrl_status_2_reg_value &= ~(1 << 7);
this->write_byte_(CONTROL_STATUS_2_REG, ctrl_status_2_reg_value);
}
void BM8563::clear_irq_() {
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_data.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t data = maybe_data.value();
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xf3);
}
void BM8563::disable_irq_() {
this->clear_irq_();
auto maybe_data = this->read_register_(CONTROL_STATUS_2_REG);
if (!maybe_data.has_value()) {
ESP_LOGE(TAG, "Failed to read CONTROL_STATUS_2_REG");
return;
}
uint8_t data = maybe_data.value();
this->write_byte_(CONTROL_STATUS_2_REG, data & 0xfc);
}
} // namespace esphome::bm8563

View File

@@ -1,57 +0,0 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome::bm8563 {
class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
void write_time();
void read_time();
void start_timer(uint32_t duration_s);
private:
void get_time_(ESPTime &time);
void get_date_(ESPTime &time);
void set_time_(const ESPTime &time);
void set_date_(const ESPTime &time);
void set_timer_irq_(uint32_t duration_s);
void clear_irq_();
void disable_irq_();
void write_byte_(uint8_t reg, uint8_t value);
void write_register_(uint8_t reg, const uint8_t *data, size_t len);
optional<uint8_t> read_register_(uint8_t reg);
uint8_t bcd2_to_byte_(uint8_t value);
uint8_t byte_to_bcd2_(uint8_t value);
};
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<BM8563> {
public:
void play(const Ts &...x) override { this->parent_->write_time(); }
};
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<BM8563> {
public:
void play(const Ts &...x) override { this->parent_->read_time(); }
};
template<typename... Ts> class TimerAction : public Action<Ts...>, public Parented<BM8563> {
public:
TEMPLATABLE_VALUE(uint32_t, duration)
void play(const Ts &...x) override {
auto duration = this->duration_.value(x...);
this->parent_->start_timer(duration);
}
};
} // namespace esphome::bm8563

View File

@@ -1,80 +0,0 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, time
import esphome.config_validation as cv
from esphome.const import CONF_DURATION, CONF_ID
DEPENDENCIES = ["i2c"]
I2C_ADDR = 0x51
bm8563_ns = cg.esphome_ns.namespace("bm8563")
BM8563 = bm8563_ns.class_("BM8563", time.RealTimeClock, i2c.I2CDevice)
WriteAction = bm8563_ns.class_("WriteAction", automation.Action)
ReadAction = bm8563_ns.class_("ReadAction", automation.Action)
TimerAction = bm8563_ns.class_("TimerAction", automation.Action)
CONFIG_SCHEMA = (
time.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BM8563),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(I2C_ADDR))
)
@automation.register_action(
"bm8563.write_time",
WriteAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(BM8563),
}
),
)
async def bm8563_write_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"bm8563.start_timer",
TimerAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(BM8563),
cv.Required(CONF_DURATION): cv.templatable(cv.positive_time_period_seconds),
}
),
)
async def bm8563_start_timer_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_DURATION], args, cg.uint32)
cg.add(var.set_duration(template_))
return var
@automation.register_action(
"bm8563.read_time",
ReadAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(BM8563),
}
),
)
async def bm8563_read_time_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await time.register_time(var, config)

View File

@@ -100,18 +100,18 @@ void BME280Component::setup() {
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (chip_id != 0x60) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(LOG_STR(BME280_ERROR_WRONG_CHIP_ID));
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
return;
}
// Send a soft reset.
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
this->mark_failed(LOG_STR("Reset failed"));
this->mark_failed("Reset failed");
return;
}
// Wait until the NVM data has finished loading.
@@ -120,12 +120,12 @@ void BME280Component::setup() {
do { // NOLINT
delay(2);
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
this->mark_failed(LOG_STR("Error reading status register"));
this->mark_failed("Error reading status register");
return;
}
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
if (status & BME280_STATUS_IM_UPDATE) {
this->mark_failed(LOG_STR("Timeout loading NVM"));
this->mark_failed("Timeout loading NVM");
return;
}
@@ -153,26 +153,26 @@ void BME280Component::setup() {
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
this->mark_failed(LOG_STR("Read humidity control"));
this->mark_failed("Read humidity control");
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
this->mark_failed(LOG_STR("Write humidity control"));
this->mark_failed("Write humidity control");
return;
}
uint8_t config_register = 0;
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
this->mark_failed(LOG_STR("Read config"));
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b101 << 5; // 1000 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
this->mark_failed(LOG_STR("Write config"));
this->mark_failed("Write config");
return;
}
}

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

@@ -65,23 +65,23 @@ void BMP280Component::setup() {
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (chip_id != 0x58) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(LOG_STR(BMP280_ERROR_WRONG_CHIP_ID));
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
return;
}
// Send a soft reset.
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed(LOG_STR("Reset failed"));
this->mark_failed("Reset failed");
return;
}
// Wait until the NVM data has finished loading.
@@ -90,12 +90,12 @@ void BMP280Component::setup() {
do {
delay(2);
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed(LOG_STR("Error reading status register"));
this->mark_failed("Error reading status register");
return;
}
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
if (status & BMP280_STATUS_IM_UPDATE) {
this->mark_failed(LOG_STR("Timeout loading NVM"));
this->mark_failed("Timeout loading NVM");
return;
}
@@ -116,14 +116,14 @@ void BMP280Component::setup() {
uint8_t config_register = 0;
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed(LOG_STR("Read config"));
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed(LOG_STR("Write config"));
this->mark_failed("Write config");
return;
}
}

View File

@@ -8,7 +8,7 @@ Camera *Camera::global_camera = nullptr;
Camera::Camera() {
if (global_camera != nullptr) {
this->status_set_error(LOG_STR("Multiple cameras are configured, but only one is supported."));
this->status_set_error("Multiple cameras are configured, but only one is supported.");
this->mark_failed();
return;
}

View File

@@ -4,7 +4,7 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
from esphome.core import CORE, ID
from esphome.core import CORE
CODEOWNERS = ["@mvturnho", "@danielschramm"]
IS_PLATFORM_COMPONENT = True
@@ -176,8 +176,5 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
else:
if isinstance(data, bytes):
data = [int(x) for x in data]
# Generate static array in flash to avoid RAM copy
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
cg.add(var.set_data_static(arr, len(data)))
cg.add(var.set_data_static(data))
return var

View File

@@ -112,16 +112,13 @@ class Canbus : public Component {
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
public:
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
this->data_.func = func;
this->len_ = -1; // Sentinel value indicates template mode
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
this->data_func_ = func;
this->static_ = false;
}
// Store pointer to static data in flash (no RAM copy)
void set_data_static(const uint8_t *data, size_t len) {
this->data_.data = data;
this->len_ = len; // Length >= 0 indicates static mode
void set_data_static(const std::vector<uint8_t> &data) {
this->data_static_ = data;
this->static_ = true;
}
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
@@ -136,26 +133,21 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
auto use_extended_id =
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
std::vector<uint8_t> data;
if (this->len_ >= 0) {
// Static mode: copy from flash to vector
data.assign(this->data_.data, this->data_.data + this->len_);
if (this->static_) {
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
} else {
// Template mode: call function
data = this->data_.func(x...);
auto val = this->data_func_(x...);
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
}
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data);
}
protected:
optional<uint32_t> can_id_{};
optional<bool> use_extended_id_{};
bool remote_transmission_request_{false};
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
union Data {
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
const uint8_t *data; // Pointer to static data in flash
} data_;
bool static_{false};
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};
};
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {

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

@@ -1,6 +1,4 @@
#include "climate.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/macros.h"
namespace esphome {
@@ -465,9 +463,6 @@ void Climate::publish_state() {
// Send state to frontend
this->state_callback_.call(*this);
#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_climate_update(this);
#endif
// Save state
this->save_state_();
}

View File

@@ -3,12 +3,7 @@ import logging
import esphome.codegen as cg
from esphome.components import climate, remote_base, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY_SENSOR,
CONF_SENSOR,
CONF_SUPPORTS_COOL,
CONF_SUPPORTS_HEAT,
)
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
from esphome.cpp_generator import MockObjClass
_LOGGER = logging.getLogger(__name__)
@@ -37,7 +32,6 @@ def climate_ir_schema(
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
}
)
.extend(cv.COMPONENT_SCHEMA)
@@ -67,9 +61,6 @@ async def register_climate_ir(var, config):
if sensor_id := config.get(CONF_SENSOR):
sens = await cg.get_variable(sensor_id)
cg.add(var.set_sensor(sens))
if sensor_id := config.get(CONF_HUMIDITY_SENSOR):
sens = await cg.get_variable(sensor_id)
cg.add(var.set_humidity_sensor(sens))
async def new_climate_ir(config, *args):

View File

@@ -11,9 +11,7 @@ climate::ClimateTraits ClimateIR::traits() {
if (this->sensor_ != nullptr) {
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
}
if (this->humidity_sensor_ != nullptr) {
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
}
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (this->supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
@@ -41,16 +39,9 @@ void ClimateIR::setup() {
this->publish_state();
});
this->current_temperature = this->sensor_->state;
} else {
this->current_temperature = NAN;
}
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->add_on_state_callback([this](float state) {
this->current_humidity = state;
// current humidity changed, publish state
this->publish_state();
});
this->current_humidity = this->humidity_sensor_->state;
}
// restore set points
auto restore = this->restore_state_();
if (restore.has_value()) {

View File

@@ -43,7 +43,6 @@ class ClimateIR : public Component,
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
protected:
float minimum_temperature_, maximum_temperature_, temperature_step_;
@@ -68,7 +67,6 @@ class ClimateIR : public Component,
climate::ClimatePresetMask presets_{};
sensor::Sensor *sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace climate_ir

View File

@@ -9,7 +9,6 @@ BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled"
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"

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

@@ -1,12 +1,9 @@
#include "cover.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include <strings.h>
#include "esphome/core/log.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
static const char *const TAG = "cover";
@@ -172,9 +169,6 @@ void Cover::publish_state(bool save) {
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
this->state_callback_.call();
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_cover_update(this);
#endif
if (save) {
CoverRestoreState restore{};
@@ -211,4 +205,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

@@ -19,14 +19,13 @@ void CST816Touchscreen::continue_setup_() {
case CST816T_CHIP_ID:
break;
default:
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
this->status_set_error(LOG_STR("Unknown chip ID"));
this->mark_failed();
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
return;
}
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
} else if (!this->skip_probe_) {
this->status_set_error(LOG_STR("Failed to read chip id"));
this->status_set_error("Failed to read chip id");
this->mark_failed();
return;
}

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

@@ -1,6 +1,5 @@
#include "date_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_DATE
#include "esphome/core/log.h"
@@ -33,9 +32,6 @@ void DateEntity::publish_state() {
this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
this->state_callback_.call();
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_date_update(this);
#endif
}
DateCall DateEntity::make_call() { return DateCall(this); }

View File

@@ -1,6 +1,5 @@
#include "datetime_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_DATETIME
#include "esphome/core/log.h"
@@ -49,9 +48,6 @@ void DateTimeEntity::publish_state() {
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
this->month_, this->day_, this->hour_, this->minute_, this->second_);
this->state_callback_.call();
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_datetime_update(this);
#endif
}
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }

View File

@@ -1,6 +1,5 @@
#include "time_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_TIME
#include "esphome/core/log.h"
@@ -30,9 +29,6 @@ void TimeEntity::publish_state() {
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
this->second_);
this->state_callback_.call();
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_time_update(this);
#endif
}
TimeCall TimeEntity::make_call() { return TimeCall(this); }

View File

@@ -59,7 +59,6 @@ async def to_code(config):
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add_define("USE_DEBUG")
FILTER_SOURCE_FILES = filter_source_files_from_platform(

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

@@ -8,7 +8,7 @@ namespace demo {
class DemoSelect : public select::Select, public Component {
protected:
void control(size_t index) override { this->publish_state(index); }
void control(const std::string &value) override { this->publish_state(value); }
};
} // namespace demo

View File

@@ -7,6 +7,7 @@
namespace esphome {
namespace display {
static const char *const TAG = "display";
const Color COLOR_OFF(0, 0, 0, 0);
@@ -15,7 +16,6 @@ const Color COLOR_ON(255, 255, 255, 255);
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void Display::clear() { this->fill(COLOR_OFF); }
void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
@@ -91,27 +91,23 @@ void HOT Display::horizontal_line(int x, int y, int width, Color color) {
for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color);
}
void HOT Display::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color);
}
void Display::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color);
}
void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color);
}
}
void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius;
int dy = 0;
@@ -135,7 +131,6 @@ void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
}
} while (dx <= 0);
}
void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius);
int dy = 0;
@@ -162,7 +157,6 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color)
}
} while (dx <= 0);
}
void Display::filled_ring(int center_x, int center_y, int radius1, int radius2, Color color) {
int rmax = radius1 > radius2 ? radius1 : radius2;
int rmin = radius1 < radius2 ? radius1 : radius2;
@@ -219,7 +213,6 @@ void Display::filled_ring(int center_x, int center_y, int radius1, int radius2,
}
} while (dxmax <= 0);
}
void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color) {
int rmax = radius1 > radius2 ? radius1 : radius2;
int rmin = radius1 < radius2 ? radius1 : radius2;
@@ -235,8 +228,7 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2,
// outer dots
this->draw_pixel_at(center_x + dxmax, center_y - dymax, color);
this->draw_pixel_at(center_x - dxmax, center_y - dymax, color);
if (dymin < rmin) {
// side parts
if (dymin < rmin) { // side parts
int lhline_width = -(dxmax - dxmin) + 1;
if (progress >= 50) {
if (float(dymax) < float(-dxmax) * tan_a) {
@@ -247,8 +239,7 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2,
this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color); // left
if (!dymax)
this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border
if (upd_dxmax > -dxmin) {
// right
if (upd_dxmax > -dxmin) { // right
int rhline_width = (upd_dxmax + dxmin) + 1;
this->horizontal_line(center_x - dxmin, center_y - dymax,
rhline_width > lhline_width ? lhline_width : rhline_width, color);
@@ -265,8 +256,7 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2,
if (lhline_width > 0)
this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color);
}
} else {
// top part
} else { // top part
int hline_width = 2 * (-dxmax) + 1;
if (progress >= 50) {
if (dymax < float(-dxmax) * tan_a) {
@@ -310,13 +300,11 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2,
}
} while (dxmax <= 0);
}
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
this->line(x1, y1, x2, y2, color);
this->line(x1, y1, x3, y3, color);
this->line(x2, y2, x3, y3, color);
}
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
if (*y1 > *y2) {
int x_temp = *x1, y_temp = *y1;
@@ -334,7 +322,6 @@ void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int
*x3 = x_temp, *y3 = y_temp;
}
}
void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
// y2 must be equal to y3 (same horizontal line)
@@ -346,8 +333,7 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3,
int s1_dy = abs(y2 - y1);
int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1;
int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1;
if (s1_dy > s1_dx) {
// swap values
if (s1_dy > s1_dx) { // swap values
int tmp = s1_dx;
s1_dx = s1_dy;
s1_dy = tmp;
@@ -363,8 +349,7 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3,
int s2_dy = abs(y3 - y1);
int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1;
int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1;
if (s2_dy > s2_dx) {
// swap values
if (s2_dy > s2_dx) { // swap values
int tmp = s2_dx;
s2_dx = s2_dy;
s2_dy = tmp;
@@ -417,25 +402,20 @@ void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3,
}
}
}
void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
// Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point
this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3);
if (y2 == y3) {
// Check for special case of a bottom-flat triangle
if (y2 == y3) { // Check for special case of a bottom-flat triangle
this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color);
} else if (y1 == y2) {
// Check for special case of a top-flat triangle
} else if (y1 == y2) { // Check for special case of a top-flat triangle
this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
} else {
// General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
} else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2;
this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
}
}
void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y,
int radius, int edges, RegularPolygonVariation variation,
float rotation_degrees) {
@@ -467,8 +447,7 @@ void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPo
int current_vertex_x, current_vertex_y;
get_regular_polygon_vertex(current_vertex_id, &current_vertex_x, &current_vertex_y, x, y, radius, edges,
variation, rotation_degrees);
if (current_vertex_id > 0) {
// Start drawing after the 2nd vertex coordinates has been calculated
if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated
if (drawing == DRAWING_FILLED) {
this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
} else if (drawing == DRAWING_OUTLINE) {
@@ -480,26 +459,21 @@ void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPo
}
}
}
void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
RegularPolygonDrawing drawing) {
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing);
}
void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) {
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing);
}
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
float rotation_degrees, Color color) {
regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED);
}
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
Color color) {
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED);
}
void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) {
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED);
}
@@ -610,19 +584,15 @@ void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, Te
break;
}
}
void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background);
}
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
this->print(x, y, font, COLOR_ON, align, text);
}
void Display::print(int x, int y, BaseFont *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
}
void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
...) {
va_list arg;
@@ -630,37 +600,31 @@ void Display::printf(int x, int y, BaseFont *font, Color color, Color background
this->vprintf_(x, y, font, color, background, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg);
va_end(arg);
}
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
va_end(arg);
}
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
void Display::set_pages(std::vector<DisplayPage *> pages) {
for (auto *page : pages)
page->set_parent(this);
@@ -673,7 +637,6 @@ void Display::set_pages(std::vector<DisplayPage *> pages) {
pages[pages.size() - 1]->set_next(pages[0]);
this->show_page(pages[0]);
}
void Display::show_page(DisplayPage *page) {
this->previous_page_ = this->page_;
this->page_ = page;
@@ -682,10 +645,8 @@ void Display::show_page(DisplayPage *page) {
t->process(this->previous_page_, this->page_);
}
}
void Display::show_next_page() { this->page_->show_next(); }
void Display::show_prev_page() { this->page_->show_prev(); }
void Display::do_update_() {
if (this->auto_clear_enabled_) {
this->clear();
@@ -699,12 +660,10 @@ void Display::do_update_() {
}
this->clear_clipping_();
}
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
ESPTime time) {
char buffer[64];
@@ -712,19 +671,15 @@ void Display::strftime(int x, int y, BaseFont *font, Color color, Color backgrou
if (ret > 0)
this->print(x, y, font, color, align, buffer, background);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, color, COLOR_OFF, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
@@ -736,7 +691,6 @@ void Display::start_clipping(Rect rect) {
}
this->clipping_rectangle_.push_back(rect);
}
void Display::end_clipping() {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "clear: Clipping is not set.");
@@ -744,7 +698,6 @@ void Display::end_clipping() {
this->clipping_rectangle_.pop_back();
}
}
void Display::extend_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
@@ -752,7 +705,6 @@ void Display::extend_clipping(Rect add_rect) {
this->clipping_rectangle_.back().extend(add_rect);
}
}
void Display::shrink_clipping(Rect add_rect) {
if (this->clipping_rectangle_.empty()) {
ESP_LOGE(TAG, "add: Clipping is not set.");
@@ -760,7 +712,6 @@ void Display::shrink_clipping(Rect add_rect) {
this->clipping_rectangle_.back().shrink(add_rect);
}
}
Rect Display::get_clipping() const {
if (this->clipping_rectangle_.empty()) {
return Rect();
@@ -768,9 +719,7 @@ Rect Display::get_clipping() const {
return this->clipping_rectangle_.back();
}
}
void Display::clear_clipping_() { this->clipping_rectangle_.clear(); }
bool Display::clip(int x, int y) {
if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
return false;
@@ -778,7 +727,6 @@ bool Display::clip(int x, int y) {
return false;
return true;
}
bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
min_x = std::max(x, 0);
max_x = std::min(x + w, this->get_width());
@@ -794,7 +742,6 @@ bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
return min_x < max_x;
}
bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
min_y = std::max(y, 0);
max_y = std::min(y + h, this->get_height());
@@ -819,15 +766,15 @@ void Display::test_card() {
int w = get_width(), h = get_height(), image_w, image_h;
this->clear();
this->show_test_card_ = false;
image_w = std::min(w - 20, 310);
image_h = std::min(h - 20, 255);
int shift_x = (w - image_w) / 2;
int shift_y = (h - image_h) / 2;
int line_w = (image_w - 6) / 6;
int image_c = image_w / 2;
if (this->get_display_type() == DISPLAY_TYPE_COLOR) {
Color r(255, 0, 0), g(0, 255, 0), b(0, 0, 255);
image_w = std::min(w - 20, 310);
image_h = std::min(h - 20, 255);
int shift_x = (w - image_w) / 2;
int shift_y = (h - image_h) / 2;
int line_w = (image_w - 6) / 6;
int image_c = image_w / 2;
for (auto i = 0; i != image_h; i++) {
int c = esp_scale(i, image_h);
this->horizontal_line(shift_x + 0, shift_y + i, line_w, r.fade_to_white(c));
@@ -839,26 +786,26 @@ void Display::test_card() {
this->horizontal_line(shift_x + image_w - (line_w * 2), shift_y + i, line_w, b.fade_to_white(c));
this->horizontal_line(shift_x + image_w - line_w, shift_y + i, line_w, b.fade_to_black(c));
}
}
this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0));
this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0));
uint16_t shift_r = shift_x + line_w - (8 * 3);
uint16_t shift_g = shift_x + image_c - (8 * 3);
uint16_t shift_b = shift_x + image_w - line_w - (8 * 3);
shift_y = h / 2 - (8 * 3);
for (auto i = 0; i < 8; i++) {
uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]);
uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]);
uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]);
for (auto k = 0; k < 8; k++) {
if ((ftr & (1 << k)) != 0) {
this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
}
if ((ftg & (1 << k)) != 0) {
this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
}
if ((ftb & (1 << k)) != 0) {
this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
uint16_t shift_r = shift_x + line_w - (8 * 3);
uint16_t shift_g = shift_x + image_c - (8 * 3);
uint16_t shift_b = shift_x + image_w - line_w - (8 * 3);
shift_y = h / 2 - (8 * 3);
for (auto i = 0; i < 8; i++) {
uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]);
uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]);
uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]);
for (auto k = 0; k < 8; k++) {
if ((ftr & (1 << k)) != 0) {
this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
}
if ((ftg & (1 << k)) != 0) {
this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
}
if ((ftb & (1 << k)) != 0) {
this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
}
}
}
}
@@ -871,9 +818,7 @@ void Display::test_card() {
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); }
void DisplayPage::show_next() {
if (this->next_ == nullptr) {
ESP_LOGE(TAG, "no next page");
@@ -881,7 +826,6 @@ void DisplayPage::show_next() {
}
this->next_->show();
}
void DisplayPage::show_prev() {
if (this->prev_ == nullptr) {
ESP_LOGE(TAG, "no previous page");
@@ -889,7 +833,6 @@ void DisplayPage::show_prev() {
}
this->prev_->show();
}
void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
@@ -925,5 +868,6 @@ const LogString *text_align_to_string(TextAlign textalign) {
return LOG_STR("UNKNOWN");
}
}
} // namespace display
} // namespace esphome

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

@@ -4,10 +4,8 @@ import pkgutil
from esphome import core, pins
import esphome.codegen as cg
from esphome.components import display, spi
from esphome.components.display import CONF_SHOW_TEST_CARD, validate_rotation
from esphome.components.mipi import flatten_sequence, map_sequence
import esphome.config_validation as cv
from esphome.config_validation import update_interval
from esphome.const import (
CONF_BUSY_PIN,
CONF_CS_PIN,
@@ -15,25 +13,15 @@ from esphome.const import (
CONF_DC_PIN,
CONF_DIMENSIONS,
CONF_ENABLE_PIN,
CONF_FULL_UPDATE_EVERY,
CONF_HEIGHT,
CONF_ID,
CONF_INIT_SEQUENCE,
CONF_LAMBDA,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_MODEL,
CONF_PAGES,
CONF_RESET_DURATION,
CONF_RESET_PIN,
CONF_ROTATION,
CONF_SWAP_XY,
CONF_TRANSFORM,
CONF_UPDATE_INTERVAL,
CONF_WIDTH,
)
from esphome.cpp_generator import RawExpression
from esphome.final_validate import full_config
from . import models
@@ -44,9 +32,8 @@ CONF_INIT_SEQUENCE_ID = "init_sequence_id"
epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi")
EPaperBase = epaper_spi_ns.class_(
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.Display
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
)
Transform = epaper_spi_ns.enum("Transform")
EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase)
EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6)
@@ -65,8 +52,6 @@ DIMENSION_SCHEMA = cv.Schema(
}
)
TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
def model_schema(config):
model = MODELS[config[CONF_MODEL]]
@@ -88,18 +73,7 @@ def model_schema(config):
)
.extend(
{
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
cv.Optional(
CONF_UPDATE_INTERVAL, default=cv.UNDEFINED
): update_interval,
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
cv.Required(CONF_MIRROR_Y): cv.boolean,
}
),
cv.Optional(CONF_FULL_UPDATE_EVERY, default=1): cv.int_range(1, 255),
model.option(CONF_DC_PIN, fallback=None): pins.gpio_output_pin_schema,
cv.GenerateID(): cv.declare_id(class_name),
cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8),
@@ -128,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)
@@ -137,29 +111,9 @@ def customise_schema(config):
CONFIG_SCHEMA = customise_schema
def _final_validate(config):
spi.final_validate_device_schema(
"epaper_spi", require_miso=False, require_mosi=True
)(config)
global_config = full_config.get()
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
if CONF_LAMBDA not in config and CONF_PAGES not in config:
if LVGL_DOMAIN in global_config:
if CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("never")
else:
# If no drawing methods are configured, and LVGL is not enabled, show a test card
config[CONF_SHOW_TEST_CARD] = True
config[CONF_UPDATE_INTERVAL] = core.TimePeriod(
seconds=60
).total_milliseconds
return config
FINAL_VALIDATE_SCHEMA = _final_validate
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
"epaper_spi", require_miso=False, require_mosi=True
)
async def to_code(config):
@@ -183,9 +137,7 @@ async def to_code(config):
init_sequence_length,
)
# Rotation is handled by setting the transform
display_config = {k: v for k, v in config.items() if k != CONF_ROTATION}
await display.register_display(var, display_config)
await display.register_display(var, config)
await spi.register_spi_device(var, config)
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
@@ -196,35 +148,11 @@ async def to_code(config):
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
if reset_pin := config.get(CONF_RESET_PIN):
reset = await cg.gpio_pin_expression(reset_pin)
if CONF_RESET_PIN in config:
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
if busy_pin := config.get(CONF_BUSY_PIN):
busy = await cg.gpio_pin_expression(busy_pin)
if CONF_BUSY_PIN in config:
busy = await cg.gpio_pin_expression(config[CONF_BUSY_PIN])
cg.add(var.set_busy_pin(busy))
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
if CONF_RESET_DURATION in config:
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))
if transform := config.get(CONF_TRANSFORM):
transform[CONF_SWAP_XY] = False
else:
transform = {x: model.get_default(x, False) for x in TRANSFORM_OPTIONS}
rotation = config[CONF_ROTATION]
if rotation == 180:
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
elif rotation == 90:
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
elif rotation == 270:
transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
transform_str = "|".join(
{
str(getattr(Transform, x.upper()))
for x in TRANSFORM_OPTIONS
if transform.get(x)
}
)
if transform_str:
cg.add(var.set_transform(RawExpression(transform_str)))

View File

@@ -9,8 +9,9 @@ namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi";
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
"IDLE", "UPDATE", "RESET", "RESET_END",
"SHOULD_WAIT", "INITIALISE", "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
};
const char *EPaperBase::epaper_state_to_string_() {
@@ -21,7 +22,7 @@ const char *EPaperBase::epaper_state_to_string_() {
void EPaperBase::setup() {
if (!this->init_buffer_(this->buffer_length_)) {
this->mark_failed(LOG_STR("Failed to initialise buffer"));
this->mark_failed("Failed to initialise buffer");
return;
}
this->setup_pins_();
@@ -68,8 +69,8 @@ void EPaperBase::data(uint8_t value) {
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty(ptr, length, '.', false).c_str());
ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty(ptr, length, '.', false).c_str());
this->dc_pin_->digital_write(false);
this->enable();
@@ -88,7 +89,7 @@ bool EPaperBase::is_idle_() const {
return !this->busy_pin_->digital_read();
}
bool EPaperBase::reset() {
bool EPaperBase::reset_() const {
if (this->reset_pin_ != nullptr) {
if (this->state_ == EPaperState::RESET) {
this->reset_pin_->digital_write(false);
@@ -104,16 +105,16 @@ void EPaperBase::update() {
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
return;
}
this->set_state_(EPaperState::UPDATE);
this->set_state_(EPaperState::RESET);
this->enable_loop();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
this->update_start_time_ = millis();
#endif
}
void EPaperBase::wait_for_idle_(bool should_wait) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
this->waiting_for_idle_start_ = millis();
if (should_wait) {
this->waiting_for_idle_start_ = millis();
this->waiting_for_idle_last_print_ = this->waiting_for_idle_start_;
}
#endif
this->waiting_for_idle_ = should_wait;
}
@@ -137,9 +138,7 @@ void EPaperBase::loop() {
if (this->waiting_for_idle_) {
if (this->is_idle_()) {
this->waiting_for_idle_ = false;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
#endif
ESP_LOGV(TAG, "Screen now idle after %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
} else {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
if (now - this->waiting_for_idle_last_print_ >= 1000) {
@@ -165,27 +164,23 @@ void EPaperBase::process_state_() {
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
switch (this->state_) {
default:
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
this->set_state_(EPaperState::IDLE);
ESP_LOGD(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
this->disable_loop();
break;
case EPaperState::IDLE:
this->disable_loop();
break;
case EPaperState::RESET:
case EPaperState::RESET_END:
if (this->reset()) {
this->set_state_(EPaperState::INITIALISE);
if (this->reset_()) {
this->set_state_(EPaperState::UPDATE);
} else {
this->set_state_(EPaperState::RESET_END, this->reset_duration_);
this->set_state_(EPaperState::RESET_END);
}
break;
case EPaperState::UPDATE:
this->do_update_(); // Calls ESPHome (current page) lambda
if (this->x_high_ < this->x_low_ || this->y_high_ < this->y_low_) {
this->set_state_(EPaperState::IDLE);
return;
}
this->set_state_(EPaperState::RESET);
this->set_state_(EPaperState::INITIALISE);
break;
case EPaperState::INITIALISE:
this->initialise_();
@@ -195,10 +190,6 @@ void EPaperBase::process_state_() {
if (!this->transfer_data()) {
return; // Not done yet, come back next loop
}
this->x_low_ = this->width_;
this->x_high_ = 0;
this->y_low_ = this->height_;
this->y_high_ = 0;
this->set_state_(EPaperState::POWER_ON);
break;
case EPaperState::POWER_ON:
@@ -206,8 +197,7 @@ void EPaperBase::process_state_() {
this->set_state_(EPaperState::REFRESH_SCREEN);
break;
case EPaperState::REFRESH_SCREEN:
this->refresh_screen(this->update_count_ != 0);
this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
this->refresh_screen();
this->set_state_(EPaperState::POWER_OFF);
break;
case EPaperState::POWER_OFF:
@@ -217,7 +207,6 @@ void EPaperBase::process_state_() {
case EPaperState::DEEP_SLEEP:
this->deep_sleep();
this->set_state_(EPaperState::IDLE);
ESP_LOGD(TAG, "Display update took %" PRIu32 " ms", millis() - this->update_start_time_);
break;
}
}
@@ -233,9 +222,6 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
}
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
TRUEFALSE(this->waiting_for_idle_));
if (state == EPaperState::IDLE) {
this->disable_loop();
}
}
void EPaperBase::start_command_() {
@@ -260,7 +246,7 @@ void EPaperBase::initialise_() {
auto length = this->init_sequence_length_;
while (index != length) {
if (length - index < 2) {
this->mark_failed(LOG_STR("Malformed init sequence"));
this->mark_failed("Malformed init sequence");
return;
}
const uint8_t cmd = sequence[index++];
@@ -274,73 +260,20 @@ void EPaperBase::initialise_() {
this->mark_failed();
return;
}
ESP_LOGV(TAG, "Command %02X, length %d", cmd, num_args);
this->cmd_data(cmd, sequence + index, num_args);
index += num_args;
}
}
}
/**
* Check and rotate coordinates based on the transform flags.
* @param x
* @param y
* @return false if the coordinates are out of bounds
*/
bool EPaperBase::rotate_coordinates_(int &x, int &y) const {
if (!this->get_clipping().inside(x, y))
return false;
if (this->transform_ & SWAP_XY)
std::swap(x, y);
if (this->transform_ & MIRROR_X)
x = this->width_ - x - 1;
if (this->transform_ & MIRROR_Y)
y = this->height_ - y - 1;
if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
return false;
return true;
}
/**
* Default implementation for monochrome displays where 8 pixels are packed to a byte.
* @param x
* @param y
* @param color
*/
void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
if (!rotate_coordinates_(x, y))
return;
const size_t pixel_position = y * this->width_ + x;
const size_t byte_position = pixel_position / 8;
const uint8_t bit_position = pixel_position % 8;
const uint8_t pixel_bit = 0x80 >> bit_position;
const auto original = this->buffer_[byte_position];
if ((color_to_bit(color) == 0)) {
this->buffer_[byte_position] = original & ~pixel_bit;
} else {
this->buffer_[byte_position] = original | pixel_bit;
}
this->x_low_ = clamp_at_most(this->x_low_, x);
this->x_high_ = clamp_at_least(this->x_high_, x + 1);
this->y_low_ = clamp_at_most(this->y_low_, y);
this->y_high_ = clamp_at_least(this->y_high_, y + 1);
}
void EPaperBase::dump_config() {
LOG_DISPLAY("", "E-Paper SPI", this);
ESP_LOGCONFIG(TAG, " Model: %s", this->name_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_PIN(" CS Pin: ", this->cs_);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG,
" SPI Data Rate: %uMHz\n"
" Full update every: %d\n"
" Swap X/Y: %s\n"
" Mirror X: %s\n"
" Mirror Y: %s",
(unsigned) (this->data_rate_ / 1000000), this->full_update_every_, YESNO(this->transform_ & SWAP_XY),
YESNO(this->transform_ & MIRROR_X), YESNO(this->transform_ & MIRROR_Y));
}
} // namespace esphome::epaper_spi

View File

@@ -5,6 +5,8 @@
#include "esphome/components/split_buffer/split_buffer.h"
#include "esphome/core/component.h"
#include <queue>
namespace esphome::epaper_spi {
using namespace display;
@@ -23,16 +25,10 @@ enum class EPaperState : uint8_t {
DEEP_SLEEP, // deep sleep the display
};
static constexpr uint8_t NONE = 0;
static constexpr uint8_t MIRROR_X = 1;
static constexpr uint8_t MIRROR_Y = 2;
static constexpr uint8_t SWAP_XY = 4;
static constexpr uint32_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
static constexpr size_t MAX_TRANSFER_SIZE = 128;
static constexpr uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
static constexpr uint8_t DELAY_FLAG = 0xFF;
class EPaperBase : public Display,
class EPaperBase : public DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_2MHZ> {
public:
@@ -49,8 +45,6 @@ class EPaperBase : public Display,
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
void set_transform(uint8_t transform) { this->transform_ = transform; }
void set_full_update_every(uint8_t full_update_every) { this->full_update_every_ = full_update_every; }
void dump_config() override;
void command(uint8_t value);
@@ -66,47 +60,20 @@ class EPaperBase : public Display,
DisplayType get_display_type() override { return this->display_type_; };
// Default implementations for monochrome displays
static uint8_t color_to_bit(Color color) {
// It's always a shade of gray. Map to BLACK or WHITE.
// We split the luminance at a suitable point
if ((static_cast<int>(color.r) + color.g + color.b) > 512) {
return 1;
}
return 0;
}
void fill(Color color) override {
auto pixel_color = color_to_bit(color) ? 0xFF : 0x00;
// We store 8 pixels per byte
this->buffer_.fill(pixel_color);
this->x_high_ = this->width_;
this->y_high_ = this->height_;
this->x_low_ = 0;
this->y_low_ = 0;
}
void clear() override {
// clear buffer to white, just like real paper.
this->fill(COLOR_ON);
}
protected:
int get_height_internal() override { return this->height_; };
int get_width_internal() override { return this->width_; };
int get_width() override { return this->transform_ & SWAP_XY ? this->height_ : this->width_; }
int get_height() override { return this->transform_ & SWAP_XY ? this->width_ : this->height_; }
void draw_pixel_at(int x, int y, Color color) override;
void process_state_();
const char *epaper_state_to_string_();
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
bool reset_() const;
void initialise_();
void wait_for_idle_(bool should_wait);
bool init_buffer_(size_t buffer_length);
bool rotate_coordinates_(int &x, int &y) const;
virtual int get_width_controller() { return this->get_width_internal(); };
/**
* Methods that must be implemented by concrete classes to control the display
@@ -119,7 +86,7 @@ class EPaperBase : public Display,
/**
* Refresh the screen after data transfer
*/
virtual void refresh_screen(bool partial) = 0;
virtual void refresh_screen() = 0;
/**
* Power the display on
@@ -151,31 +118,24 @@ class EPaperBase : public Display,
DisplayType display_type_;
size_t buffer_length_{};
size_t current_data_index_{}; // used by data transfer to track progress
split_buffer::SplitBuffer buffer_{};
size_t current_data_index_{0}; // used by data transfer to track progress
uint32_t reset_duration_{200};
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
uint32_t transfer_start_time_{};
uint32_t waiting_for_idle_last_print_{0};
uint32_t waiting_for_idle_start_{0};
#endif
GPIOPin *dc_pin_{};
GPIOPin *busy_pin_{};
GPIOPin *reset_pin_{};
bool waiting_for_idle_{};
uint32_t delay_until_{};
uint8_t transform_{};
uint8_t update_count_{};
// these values represent the bounds of the updated buffer. Note that x_high and y_high
// point to the pixel past the last one updated, i.e. may range up to width/height.
uint16_t x_low_{}, y_low_{}, x_high_{}, y_high_{};
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
uint32_t waiting_for_idle_last_print_{};
uint32_t waiting_for_idle_start_{};
#endif
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
uint32_t update_start_time_{};
#endif
bool waiting_for_idle_{false};
uint32_t delay_until_{0};
split_buffer::SplitBuffer buffer_;
// properties with specific initialisers go last
EPaperState state_{EPaperState::IDLE};
uint32_t reset_duration_{10};
uint8_t full_update_every_{1};
};
} // namespace esphome::epaper_spi

View File

@@ -6,6 +6,7 @@
namespace esphome::epaper_spi {
static constexpr const char *const TAG = "epaper_spi.6c";
static constexpr size_t MAX_TRANSFER_SIZE = 128;
static constexpr unsigned char GRAY_THRESHOLD = 50;
enum E6Color {
@@ -74,24 +75,24 @@ static uint8_t color_to_hex(Color color) {
}
void EPaperSpectraE6::power_on() {
ESP_LOGV(TAG, "Power on");
ESP_LOGD(TAG, "Power on");
this->command(0x04);
}
void EPaperSpectraE6::power_off() {
ESP_LOGV(TAG, "Power off");
ESP_LOGD(TAG, "Power off");
this->command(0x02);
this->data(0x00);
}
void EPaperSpectraE6::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh");
void EPaperSpectraE6::refresh_screen() {
ESP_LOGD(TAG, "Refresh");
this->command(0x12);
this->data(0x00);
}
void EPaperSpectraE6::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep");
ESP_LOGD(TAG, "Deep sleep");
this->command(0x07);
this->data(0xA5);
}
@@ -108,11 +109,12 @@ void EPaperSpectraE6::clear() {
this->fill(COLOR_ON);
}
void HOT EPaperSpectraE6::draw_pixel_at(int x, int y, Color color) {
if (!this->rotate_coordinates_(x, y))
void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
return;
auto pixel_bits = color_to_hex(color);
uint32_t pixel_position = x + y * this->get_width_internal();
uint32_t pixel_position = x + y * this->get_width_controller();
uint32_t byte_position = pixel_position / 2;
auto original = this->buffer_[byte_position];
if ((pixel_position & 1) != 0) {
@@ -126,6 +128,10 @@ bool HOT EPaperSpectraE6::transfer_data() {
const uint32_t start_time = App.get_loop_component_start_time();
const size_t buffer_length = this->buffer_length_;
if (this->current_data_index_ == 0) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
this->transfer_start_time_ = millis();
#endif
ESP_LOGV(TAG, "Start sending data at %ums", (unsigned) millis());
this->command(0x10);
}
@@ -154,6 +160,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
this->end_data_();
}
this->current_data_index_ = 0;
ESP_LOGV(TAG, "Sent data in %" PRIu32 " ms", millis() - this->transfer_start_time_);
return true;
}
} // namespace esphome::epaper_spi

View File

@@ -16,11 +16,11 @@ class EPaperSpectraE6 : public EPaperBase {
void clear() override;
protected:
void refresh_screen(bool partial) override;
void refresh_screen() override;
void power_on() override;
void power_off() override;
void deep_sleep() override;
void draw_pixel_at(int x, int y, Color color) override;
void draw_absolute_pixel_internal(int x, int y, Color color) override;
bool transfer_data() override;
};

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