mirror of
https://github.com/esphome/esphome.git
synced 2025-11-13 21:35:48 +00:00
Compare commits
83 Commits
beta
...
mqtt_reduc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d497566e6 | ||
|
|
5ad22620c9 | ||
|
|
e70cb098ae | ||
|
|
7f2d8a2c11 | ||
|
|
4f4da1de22 | ||
|
|
f9807db08a | ||
|
|
541fb8b27c | ||
|
|
85e0a4fbf9 | ||
|
|
7e54803ede | ||
|
|
a078486a87 | ||
|
|
ba18bb6a4f | ||
|
|
07ad32968e | ||
|
|
0b077bdfc6 | ||
|
|
1f00617738 | ||
|
|
9cf1fd24fd | ||
|
|
bbd636a8cc | ||
|
|
322dc530a9 | ||
|
|
0b09e50685 | ||
|
|
a96cc5e6f2 | ||
|
|
9a4288d81a | ||
|
|
b95999aca7 | ||
|
|
c70937ed01 | ||
|
|
3151606d50 | ||
|
|
5080698c3a | ||
|
|
931e3f80f0 | ||
|
|
cd93f7f55a | ||
|
|
d98b00f56d | ||
|
|
8fd43f1d96 | ||
|
|
0475ec5533 | ||
|
|
6fe5a0c736 | ||
|
|
1ec9383abe | ||
|
|
558d4eb9dd | ||
|
|
c6ecfd0c55 | ||
|
|
3b8b2c0754 | ||
|
|
f5d69a2539 | ||
|
|
29b9073d62 | ||
|
|
a45e94cd06 | ||
|
|
71f2fb8353 | ||
|
|
0fcae15c25 | ||
|
|
a1d6bac21a | ||
|
|
db69ce24ae | ||
|
|
293400ee14 | ||
|
|
57bf3f968f | ||
|
|
922c2bcd5a | ||
|
|
5e9b972831 | ||
|
|
3bc0041b94 | ||
|
|
daa03e5b3c | ||
|
|
62ce39e430 | ||
|
|
a9e5e4d6d2 | ||
|
|
95a0c9594f | ||
|
|
8762d7cf0e | ||
|
|
84316d62f9 | ||
|
|
e1e047c53f | ||
|
|
b0ada914bc | ||
|
|
e2101f5a20 | ||
|
|
f87c969b43 | ||
|
|
f011c44130 | ||
|
|
843f590db4 | ||
|
|
2c86ebaf7f | ||
|
|
25fe4a1476 | ||
|
|
86c12079b4 | ||
|
|
79aafe2cd5 | ||
|
|
a5d6e39b2f | ||
|
|
a78a7dfa4e | ||
|
|
7879df4dd1 | ||
|
|
43c62297e8 | ||
|
|
5049c7227d | ||
|
|
256d3b119b | ||
|
|
6d2c700c43 | ||
|
|
9d081795e8 | ||
|
|
59848a2c8a | ||
|
|
c7c408e667 | ||
|
|
acfa325f23 | ||
|
|
cb97271704 | ||
|
|
8e6ee2bed1 | ||
|
|
354f46f7c0 | ||
|
|
7b6acd3c00 | ||
|
|
11f5f7683c | ||
|
|
5da589abd0 | ||
|
|
daa39a489d | ||
|
|
3bb95a190d | ||
|
|
25a6202bb9 | ||
|
|
c4eeed7f7e |
@@ -51,79 +51,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
* **Naming Conventions:**
|
* **Naming Conventions:**
|
||||||
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
|
* **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):
|
* **C++:** Follows the Google C++ Style Guide.
|
||||||
- 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
|
|
||||||
|
|
||||||
* **Component Structure:**
|
* **Component Structure:**
|
||||||
* **Standard Files:**
|
* **Standard Files:**
|
||||||
@@ -172,7 +100,8 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
* **C++ Class Pattern:**
|
* **C++ Class Pattern:**
|
||||||
```cpp
|
```cpp
|
||||||
namespace esphome::my_component {
|
namespace esphome {
|
||||||
|
namespace my_component {
|
||||||
|
|
||||||
class MyComponent : public Component {
|
class MyComponent : public Component {
|
||||||
public:
|
public:
|
||||||
@@ -188,7 +117,8 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
int param_{0};
|
int param_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::my_component
|
} // namespace my_component
|
||||||
|
} // namespace esphome
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Common Component Examples:**
|
* **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`.
|
* **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`.
|
* **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.
|
* **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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c
|
d7693a1e996cacd4a3d1c9a16336799c2a8cc3db02e4e74084151ce964581248
|
||||||
|
|||||||
22
.github/workflows/auto-label-pr.yml
vendored
22
.github/workflows/auto-label-pr.yml
vendored
@@ -53,7 +53,6 @@ jobs:
|
|||||||
'new-target-platform',
|
'new-target-platform',
|
||||||
'merging-to-release',
|
'merging-to-release',
|
||||||
'merging-to-beta',
|
'merging-to-beta',
|
||||||
'chained-pr',
|
|
||||||
'core',
|
'core',
|
||||||
'small-pr',
|
'small-pr',
|
||||||
'dashboard',
|
'dashboard',
|
||||||
@@ -141,8 +140,6 @@ jobs:
|
|||||||
labels.add('merging-to-release');
|
labels.add('merging-to-release');
|
||||||
} else if (baseRef === 'beta') {
|
} else if (baseRef === 'beta') {
|
||||||
labels.add('merging-to-beta');
|
labels.add('merging-to-beta');
|
||||||
} else if (baseRef !== 'dev') {
|
|
||||||
labels.add('chained-pr');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return labels;
|
return labels;
|
||||||
@@ -416,7 +413,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate review messages
|
// Generate review messages
|
||||||
function generateReviewMessages(finalLabels, originalLabelCount) {
|
function generateReviewMessages(finalLabels) {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
@@ -430,15 +427,15 @@ jobs:
|
|||||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||||
|
|
||||||
const tooManyLabels = originalLabelCount > MAX_LABELS;
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||||
|
|
||||||
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
||||||
|
|
||||||
if (tooManyLabels && tooManyChanges) {
|
if (tooManyLabels && tooManyChanges) {
|
||||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
|
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${finalLabels.length} different components/areas.`;
|
||||||
} else if (tooManyLabels) {
|
} else if (tooManyLabels) {
|
||||||
message += `This PR affects ${originalLabelCount} different components/areas.`;
|
message += `This PR affects ${finalLabels.length} different components/areas.`;
|
||||||
} else {
|
} else {
|
||||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
|
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
|
||||||
}
|
}
|
||||||
@@ -466,8 +463,8 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle reviews
|
// Handle reviews
|
||||||
async function handleReviews(finalLabels, originalLabelCount) {
|
async function handleReviews(finalLabels) {
|
||||||
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
|
const reviewMessages = generateReviewMessages(finalLabels);
|
||||||
const hasReviewableLabels = finalLabels.some(label =>
|
const hasReviewableLabels = finalLabels.some(label =>
|
||||||
['too-big', 'needs-codeowners'].includes(label)
|
['too-big', 'needs-codeowners'].includes(label)
|
||||||
);
|
);
|
||||||
@@ -531,8 +528,8 @@ jobs:
|
|||||||
const apiData = await fetchApiData();
|
const apiData = await fetchApiData();
|
||||||
const baseRef = context.payload.pull_request.base.ref;
|
const baseRef = context.payload.pull_request.base.ref;
|
||||||
|
|
||||||
// Early exit for release and beta branches only
|
// Early exit for non-dev branches
|
||||||
if (baseRef === 'release' || baseRef === 'beta') {
|
if (baseRef !== 'dev') {
|
||||||
const branchLabels = await detectMergeBranch();
|
const branchLabels = await detectMergeBranch();
|
||||||
const finalLabels = Array.from(branchLabels);
|
const finalLabels = Array.from(branchLabels);
|
||||||
|
|
||||||
@@ -627,7 +624,6 @@ jobs:
|
|||||||
|
|
||||||
// Handle too many labels (only for non-mega PRs)
|
// Handle too many labels (only for non-mega PRs)
|
||||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
const originalLabelCount = finalLabels.length;
|
|
||||||
|
|
||||||
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
|
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
|
||||||
finalLabels = ['too-big'];
|
finalLabels = ['too-big'];
|
||||||
@@ -636,7 +632,7 @@ jobs:
|
|||||||
console.log('Computed labels:', finalLabels.join(', '));
|
console.log('Computed labels:', finalLabels.join(', '));
|
||||||
|
|
||||||
// Handle reviews
|
// Handle reviews
|
||||||
await handleReviews(finalLabels, originalLabelCount);
|
await handleReviews(finalLabels);
|
||||||
|
|
||||||
// Apply labels
|
// Apply labels
|
||||||
if (finalLabels.length > 0) {
|
if (finalLabels.length > 0) {
|
||||||
|
|||||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
run: git diff
|
run: git diff
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Archive artifacts
|
name: Archive artifacts
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: generated-proto-files
|
name: generated-proto-files
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
111
.github/workflows/ci-memory-impact-comment.yml
vendored
111
.github/workflows/ci-memory-impact-comment.yml
vendored
@@ -1,111 +0,0 @@
|
|||||||
---
|
|
||||||
name: Memory Impact Comment (Forks)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["CI"]
|
|
||||||
types: [completed]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
actions: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
memory-impact-comment:
|
|
||||||
name: Post memory impact comment (fork PRs only)
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
# Only run for PRs from forks that had successful CI runs
|
|
||||||
if: >
|
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
github.event.workflow_run.head_repository.full_name != github.repository
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
steps:
|
|
||||||
- name: Get PR details
|
|
||||||
id: pr
|
|
||||||
run: |
|
|
||||||
# Get PR details by searching for PR with matching head SHA
|
|
||||||
# The workflow_run.pull_requests field is often empty for forks
|
|
||||||
# Use paginate to handle repos with many open PRs
|
|
||||||
head_sha="${{ github.event.workflow_run.head_sha }}"
|
|
||||||
pr_data=$(gh api --paginate "/repos/${{ github.repository }}/pulls" \
|
|
||||||
--jq ".[] | select(.head.sha == \"$head_sha\") | {number: .number, base_ref: .base.ref}" \
|
|
||||||
| head -n 1)
|
|
||||||
|
|
||||||
if [ -z "$pr_data" ]; then
|
|
||||||
echo "No PR found for SHA $head_sha, skipping"
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
pr_number=$(echo "$pr_data" | jq -r '.number')
|
|
||||||
base_ref=$(echo "$pr_data" | jq -r '.base_ref')
|
|
||||||
|
|
||||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Found PR #$pr_number targeting base branch: $base_ref"
|
|
||||||
|
|
||||||
- name: Check out code from base repository
|
|
||||||
if: steps.pr.outputs.skip != 'true'
|
|
||||||
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
|
|
||||||
repository: ${{ github.repository }}
|
|
||||||
ref: ${{ steps.pr.outputs.base_ref }}
|
|
||||||
|
|
||||||
- name: Restore Python
|
|
||||||
if: steps.pr.outputs.skip != 'true'
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: "3.11"
|
|
||||||
cache-key: ${{ hashFiles('.cache-key') }}
|
|
||||||
|
|
||||||
- name: Download memory analysis artifacts
|
|
||||||
if: steps.pr.outputs.skip != 'true'
|
|
||||||
run: |
|
|
||||||
run_id="${{ github.event.workflow_run.id }}"
|
|
||||||
echo "Downloading artifacts from workflow run $run_id"
|
|
||||||
|
|
||||||
mkdir -p memory-analysis
|
|
||||||
|
|
||||||
# Download target analysis artifact
|
|
||||||
if gh run download --name "memory-analysis-target" --dir memory-analysis --repo "${{ github.repository }}" "$run_id"; then
|
|
||||||
echo "Downloaded memory-analysis-target artifact."
|
|
||||||
else
|
|
||||||
echo "No memory-analysis-target artifact found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Download PR analysis artifact
|
|
||||||
if gh run download --name "memory-analysis-pr" --dir memory-analysis --repo "${{ github.repository }}" "$run_id"; then
|
|
||||||
echo "Downloaded memory-analysis-pr artifact."
|
|
||||||
else
|
|
||||||
echo "No memory-analysis-pr artifact found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check if artifacts exist
|
|
||||||
id: check
|
|
||||||
if: steps.pr.outputs.skip != 'true'
|
|
||||||
run: |
|
|
||||||
if [ -f ./memory-analysis/memory-analysis-target.json ] && [ -f ./memory-analysis/memory-analysis-pr.json ]; then
|
|
||||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Memory analysis artifacts not found, skipping comment"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Post or update PR comment
|
|
||||||
if: steps.pr.outputs.skip != 'true' && steps.check.outputs.found == 'true'
|
|
||||||
env:
|
|
||||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
# Pass PR number and JSON file paths directly to Python script
|
|
||||||
# Let Python parse the JSON to avoid shell injection risks
|
|
||||||
# The script will validate and sanitize all inputs
|
|
||||||
python script/ci_memory_impact_comment.py \
|
|
||||||
--pr-number "$PR_NUMBER" \
|
|
||||||
--target-json ./memory-analysis/memory-analysis-target.json \
|
|
||||||
--pr-json ./memory-analysis/memory-analysis-pr.json
|
|
||||||
315
.github/workflows/ci.yml
vendored
315
.github/workflows/ci.yml
vendored
@@ -114,7 +114,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
python-version:
|
python-version:
|
||||||
- "3.11"
|
- "3.11"
|
||||||
- "3.13"
|
- "3.14"
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
- macOS-latest
|
- macOS-latest
|
||||||
@@ -123,9 +123,9 @@ jobs:
|
|||||||
# Minimize CI resource usage
|
# Minimize CI resource usage
|
||||||
# by only running the Python version
|
# by only running the Python version
|
||||||
# version used for docker images on Windows and macOS
|
# version used for docker images on Windows and macOS
|
||||||
- python-version: "3.13"
|
- python-version: "3.14"
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
- python-version: "3.13"
|
- python-version: "3.14"
|
||||||
os: macOS-latest
|
os: macOS-latest
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs:
|
needs:
|
||||||
@@ -170,17 +170,12 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
||||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||||
clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }}
|
|
||||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||||
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||||
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
||||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||||
changed-cpp-file-count: ${{ steps.determine.outputs.changed-cpp-file-count }}
|
|
||||||
memory_impact: ${{ steps.determine.outputs.memory-impact }}
|
memory_impact: ${{ steps.determine.outputs.memory-impact }}
|
||||||
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
|
||||||
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
|
||||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
@@ -192,11 +187,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Restore components graph cache
|
|
||||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: .temp/components_graph.json
|
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
|
||||||
- name: Determine which tests to run
|
- name: Determine which tests to run
|
||||||
id: determine
|
id: determine
|
||||||
env:
|
env:
|
||||||
@@ -210,23 +200,12 @@ jobs:
|
|||||||
# Extract individual fields
|
# Extract individual fields
|
||||||
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||||
echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT
|
|
||||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-cpp-file-count=$(echo "$output" | jq -r '.changed_cpp_file_count')" >> $GITHUB_OUTPUT
|
|
||||||
echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT
|
echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT
|
||||||
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
|
||||||
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
|
||||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
|
||||||
- name: Save components graph cache
|
|
||||||
if: github.ref == 'refs/heads/dev'
|
|
||||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: .temp/components_graph.json
|
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
@@ -264,34 +243,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||||
|
|
||||||
cpp-unit-tests:
|
clang-tidy:
|
||||||
name: Run C++ unit tests
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- determine-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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
|
|
||||||
- name: Run cpp_unit_test.py
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
if [ "${{ needs.determine-jobs.outputs.cpp-unit-tests-run-all }}" = "true" ]; then
|
|
||||||
script/cpp_unit_test.py --all
|
|
||||||
else
|
|
||||||
ARGS=$(echo '${{ needs.determine-jobs.outputs.cpp-unit-tests-components }}' | jq -r '.[] | @sh' | xargs)
|
|
||||||
script/cpp_unit_test.py $ARGS
|
|
||||||
fi
|
|
||||||
|
|
||||||
clang-tidy-single:
|
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
@@ -309,6 +261,22 @@ jobs:
|
|||||||
name: Run script/clang-tidy for ESP8266
|
name: Run script/clang-tidy for ESP8266
|
||||||
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
||||||
pio_cache_key: tidyesp8266
|
pio_cache_key: tidyesp8266
|
||||||
|
- id: clang-tidy
|
||||||
|
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
||||||
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
||||||
|
pio_cache_key: tidyesp32
|
||||||
|
- id: clang-tidy
|
||||||
|
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
||||||
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
||||||
|
pio_cache_key: tidyesp32
|
||||||
|
- id: clang-tidy
|
||||||
|
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
||||||
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
||||||
|
pio_cache_key: tidyesp32
|
||||||
|
- id: clang-tidy
|
||||||
|
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
||||||
|
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
||||||
|
pio_cache_key: tidyesp32
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
name: Run script/clang-tidy for ESP32 IDF
|
name: Run script/clang-tidy for ESP32 IDF
|
||||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||||
@@ -389,165 +357,45 @@ jobs:
|
|||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
clang-tidy-nosplit:
|
test-build-components-splitter:
|
||||||
name: Run script/clang-tidy for ESP32 Arduino
|
name: Split components for intelligent grouping (40 weighted per batch)
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
- determine-jobs
|
||||||
if: needs.determine-jobs.outputs.clang-tidy-mode == 'nosplit'
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
||||||
env:
|
outputs:
|
||||||
GH_TOKEN: ${{ github.token }}
|
matrix: ${{ steps.split.outputs.components }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Split components intelligently based on bus configurations
|
||||||
- name: Cache platformio
|
id: split
|
||||||
if: github.ref == 'refs/heads/dev'
|
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: ~/.platformio
|
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
|
||||||
|
|
||||||
- name: Cache platformio
|
|
||||||
if: github.ref != 'refs/heads/dev'
|
|
||||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: ~/.platformio
|
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
|
||||||
|
|
||||||
- name: Register problem matchers
|
|
||||||
run: |
|
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
|
||||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
|
||||||
|
|
||||||
- name: Check if full clang-tidy scan needed
|
|
||||||
id: check_full_scan
|
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
if python script/clang_tidy_hash.py --check; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
# Use intelligent splitter that groups components with same bus configs
|
||||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
|
||||||
|
|
||||||
|
# Only isolate directly changed components when targeting dev branch
|
||||||
|
# For beta/release branches, group everything for faster CI
|
||||||
|
if [[ "${{ github.base_ref }}" == beta* ]] || [[ "${{ github.base_ref }}" == release* ]]; then
|
||||||
|
directly_changed='[]'
|
||||||
|
echo "Target branch: ${{ github.base_ref }} - grouping all components"
|
||||||
else
|
else
|
||||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
|
||||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
echo "Target branch: ${{ github.base_ref }} - isolating directly changed components"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run clang-tidy
|
echo "Splitting components intelligently..."
|
||||||
run: |
|
output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github)
|
||||||
. venv/bin/activate
|
|
||||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
|
||||||
echo "Running FULL clang-tidy scan (hash changed)"
|
|
||||||
script/clang-tidy --all-headers --fix --environment esp32-arduino-tidy
|
|
||||||
else
|
|
||||||
echo "Running clang-tidy on changed files only"
|
|
||||||
script/clang-tidy --all-headers --fix --changed --environment esp32-arduino-tidy
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
|
||||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
|
||||||
|
|
||||||
- name: Suggested changes
|
echo "$output" >> $GITHUB_OUTPUT
|
||||||
run: script/ci-suggest-changes
|
|
||||||
if: always()
|
|
||||||
|
|
||||||
clang-tidy-split:
|
|
||||||
name: ${{ matrix.name }}
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- determine-jobs
|
|
||||||
if: needs.determine-jobs.outputs.clang-tidy-mode == 'split'
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 2
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- id: clang-tidy
|
|
||||||
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
|
||||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
|
||||||
- id: clang-tidy
|
|
||||||
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
|
||||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
|
||||||
- id: clang-tidy
|
|
||||||
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
|
||||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
|
||||||
- id: clang-tidy
|
|
||||||
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
|
||||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
with:
|
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
|
|
||||||
- name: Cache platformio
|
|
||||||
if: github.ref == 'refs/heads/dev'
|
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: ~/.platformio
|
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
|
||||||
|
|
||||||
- name: Cache platformio
|
|
||||||
if: github.ref != 'refs/heads/dev'
|
|
||||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
|
||||||
with:
|
|
||||||
path: ~/.platformio
|
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
|
||||||
|
|
||||||
- name: Register problem matchers
|
|
||||||
run: |
|
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
|
||||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
|
||||||
|
|
||||||
- name: Check if full clang-tidy scan needed
|
|
||||||
id: check_full_scan
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
if python script/clang_tidy_hash.py --check; then
|
|
||||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run clang-tidy
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
|
||||||
echo "Running FULL clang-tidy scan (hash changed)"
|
|
||||||
script/clang-tidy --all-headers --fix ${{ matrix.options }}
|
|
||||||
else
|
|
||||||
echo "Running clang-tidy on changed files only"
|
|
||||||
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }}
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
|
||||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
|
||||||
|
|
||||||
- name: Suggested changes
|
|
||||||
run: script/ci-suggest-changes
|
|
||||||
if: always()
|
|
||||||
|
|
||||||
test-build-components-split:
|
test-build-components-split:
|
||||||
name: Test components batch (${{ matrix.components }})
|
name: Test components batch (${{ matrix.components }})
|
||||||
@@ -555,12 +403,13 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- determine-jobs
|
- determine-jobs
|
||||||
|
- test-build-components-splitter
|
||||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
||||||
matrix:
|
matrix:
|
||||||
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Show disk space
|
- name: Show disk space
|
||||||
run: |
|
run: |
|
||||||
@@ -792,12 +641,6 @@ jobs:
|
|||||||
--output-env \
|
--output-env \
|
||||||
--output-json memory-analysis-target.json
|
--output-json memory-analysis-target.json
|
||||||
|
|
||||||
# Add metadata to JSON before caching
|
|
||||||
python script/ci_add_metadata_to_json.py \
|
|
||||||
--json-file memory-analysis-target.json \
|
|
||||||
--components "$components" \
|
|
||||||
--platform "$platform"
|
|
||||||
|
|
||||||
- name: Save memory analysis to cache
|
- name: Save memory analysis to cache
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
@@ -821,7 +664,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload memory analysis JSON
|
- name: Upload memory analysis JSON
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: memory-analysis-target
|
name: memory-analysis-target
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
@@ -877,15 +720,8 @@ jobs:
|
|||||||
python script/ci_memory_impact_extract.py \
|
python script/ci_memory_impact_extract.py \
|
||||||
--output-env \
|
--output-env \
|
||||||
--output-json memory-analysis-pr.json
|
--output-json memory-analysis-pr.json
|
||||||
|
|
||||||
# Add metadata to JSON (components and platform are in shell variables above)
|
|
||||||
python script/ci_add_metadata_to_json.py \
|
|
||||||
--json-file memory-analysis-pr.json \
|
|
||||||
--components "$components" \
|
|
||||||
--platform "$platform"
|
|
||||||
|
|
||||||
- name: Upload memory analysis JSON
|
- name: Upload memory analysis JSON
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: memory-analysis-pr
|
name: memory-analysis-pr
|
||||||
path: memory-analysis-pr.json
|
path: memory-analysis-pr.json
|
||||||
@@ -900,12 +736,10 @@ jobs:
|
|||||||
- determine-jobs
|
- determine-jobs
|
||||||
- memory-impact-target-branch
|
- memory-impact-target-branch
|
||||||
- memory-impact-pr-branch
|
- memory-impact-pr-branch
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && fromJSON(needs.determine-jobs.outputs.memory_impact).should_run == 'true' && needs.memory-impact-target-branch.outputs.skip != 'true'
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.memory_impact).should_run == 'true' && needs.memory-impact-target-branch.outputs.skip != 'true'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
@@ -915,29 +749,65 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Download target analysis JSON
|
- name: Download target analysis JSON
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: memory-analysis-target
|
name: memory-analysis-target
|
||||||
path: ./memory-analysis
|
path: ./memory-analysis
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Download PR analysis JSON
|
- name: Download PR analysis JSON
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: memory-analysis-pr
|
name: memory-analysis-pr
|
||||||
path: ./memory-analysis
|
path: ./memory-analysis
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Post or update PR comment
|
- name: Post or update PR comment
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
COMPONENTS: ${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}
|
||||||
|
PLATFORM: ${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}
|
||||||
|
TARGET_RAM: ${{ needs.memory-impact-target-branch.outputs.ram_usage }}
|
||||||
|
TARGET_FLASH: ${{ needs.memory-impact-target-branch.outputs.flash_usage }}
|
||||||
|
PR_RAM: ${{ needs.memory-impact-pr-branch.outputs.ram_usage }}
|
||||||
|
PR_FLASH: ${{ needs.memory-impact-pr-branch.outputs.flash_usage }}
|
||||||
|
TARGET_CACHE_HIT: ${{ needs.memory-impact-target-branch.outputs.cache_hit }}
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
# Pass JSON file paths directly to Python script
|
# Check if analysis JSON files exist
|
||||||
# All data is extracted from JSON files for security
|
target_json_arg=""
|
||||||
|
pr_json_arg=""
|
||||||
|
|
||||||
|
if [ -f ./memory-analysis/memory-analysis-target.json ]; then
|
||||||
|
echo "Found target analysis JSON"
|
||||||
|
target_json_arg="--target-json ./memory-analysis/memory-analysis-target.json"
|
||||||
|
else
|
||||||
|
echo "No target analysis JSON found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f ./memory-analysis/memory-analysis-pr.json ]; then
|
||||||
|
echo "Found PR analysis JSON"
|
||||||
|
pr_json_arg="--pr-json ./memory-analysis/memory-analysis-pr.json"
|
||||||
|
else
|
||||||
|
echo "No PR analysis JSON found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add cache flag if target was cached
|
||||||
|
cache_flag=""
|
||||||
|
if [ "$TARGET_CACHE_HIT" == "true" ]; then
|
||||||
|
cache_flag="--target-cache-hit"
|
||||||
|
fi
|
||||||
|
|
||||||
python script/ci_memory_impact_comment.py \
|
python script/ci_memory_impact_comment.py \
|
||||||
--pr-number "$PR_NUMBER" \
|
--pr-number "${{ github.event.pull_request.number }}" \
|
||||||
--target-json ./memory-analysis/memory-analysis-target.json \
|
--components "$COMPONENTS" \
|
||||||
--pr-json ./memory-analysis/memory-analysis-pr.json
|
--platform "$PLATFORM" \
|
||||||
|
--target-ram "$TARGET_RAM" \
|
||||||
|
--target-flash "$TARGET_FLASH" \
|
||||||
|
--pr-ram "$PR_RAM" \
|
||||||
|
--pr-flash "$PR_FLASH" \
|
||||||
|
$target_json_arg \
|
||||||
|
$pr_json_arg \
|
||||||
|
$cache_flag
|
||||||
|
|
||||||
ci-status:
|
ci-status:
|
||||||
name: CI Status
|
name: CI Status
|
||||||
@@ -948,10 +818,9 @@ jobs:
|
|||||||
- pylint
|
- pylint
|
||||||
- pytest
|
- pytest
|
||||||
- integration-tests
|
- integration-tests
|
||||||
- clang-tidy-single
|
- clang-tidy
|
||||||
- clang-tidy-nosplit
|
|
||||||
- clang-tidy-split
|
|
||||||
- determine-jobs
|
- determine-jobs
|
||||||
|
- test-build-components-splitter
|
||||||
- test-build-components-split
|
- test-build-components-split
|
||||||
- pre-commit-ci-lite
|
- pre-commit-ci-lite
|
||||||
- memory-impact-target-branch
|
- memory-impact-target-branch
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
request-codeowner-reviews:
|
request-codeowner-reviews:
|
||||||
name: Run
|
name: Run
|
||||||
if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }}
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Request reviews from component codeowners
|
- name: Request reviews from component codeowners
|
||||||
|
|||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -86,6 +86,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
|||||||
# version: ${{ needs.init.outputs.tag }}
|
# version: ${{ needs.init.outputs.tag }}
|
||||||
|
|
||||||
- name: Upload digests
|
- name: Upload digests
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: digests-${{ matrix.platform.arch }}
|
name: digests-${{ matrix.platform.arch }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
@@ -171,7 +171,7 @@ jobs:
|
|||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
|
|||||||
1
.github/workflows/status-check-labels.yml
vendored
1
.github/workflows/status-check-labels.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
|||||||
label:
|
label:
|
||||||
- needs-docs
|
- needs-docs
|
||||||
- merge-after-release
|
- merge-after-release
|
||||||
- chained-pr
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check for ${{ matrix.label }} label
|
- name: Check for ${{ matrix.label }} label
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.14.4
|
rev: v0.14.1
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
10
CODEOWNERS
10
CODEOWNERS
@@ -70,7 +70,6 @@ esphome/components/bl0939/* @ziceva
|
|||||||
esphome/components/bl0940/* @dan-s-github @tobias-
|
esphome/components/bl0940/* @dan-s-github @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||||
esphome/components/ble_nus/* @tomaszduda23
|
|
||||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||||
esphome/components/bme280_base/* @esphome/core
|
esphome/components/bme280_base/* @esphome/core
|
||||||
esphome/components/bme280_spi/* @apbodrov
|
esphome/components/bme280_spi/* @apbodrov
|
||||||
@@ -155,14 +154,12 @@ esphome/components/esp32_ble_tracker/* @bdraco
|
|||||||
esphome/components/esp32_camera_web_server/* @ayufan
|
esphome/components/esp32_camera_web_server/* @ayufan
|
||||||
esphome/components/esp32_can/* @Sympatron
|
esphome/components/esp32_can/* @Sympatron
|
||||||
esphome/components/esp32_hosted/* @swoboda1337
|
esphome/components/esp32_hosted/* @swoboda1337
|
||||||
esphome/components/esp32_hosted/update/* @swoboda1337
|
|
||||||
esphome/components/esp32_improv/* @jesserockz
|
esphome/components/esp32_improv/* @jesserockz
|
||||||
esphome/components/esp32_rmt/* @jesserockz
|
esphome/components/esp32_rmt/* @jesserockz
|
||||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||||
esphome/components/esp8266/* @esphome/core
|
esphome/components/esp8266/* @esphome/core
|
||||||
esphome/components/esp_ldo/* @clydebarrow
|
esphome/components/esp_ldo/* @clydebarrow
|
||||||
esphome/components/espnow/* @jesserockz
|
esphome/components/espnow/* @jesserockz
|
||||||
esphome/components/espnow/packet_transport/* @EasilyBoredEngineer
|
|
||||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||||
esphome/components/event/* @nohat
|
esphome/components/event/* @nohat
|
||||||
esphome/components/exposure_notifications/* @OttoWinter
|
esphome/components/exposure_notifications/* @OttoWinter
|
||||||
@@ -181,7 +178,7 @@ esphome/components/gdk101/* @Szewcson
|
|||||||
esphome/components/gl_r01_i2c/* @pkejval
|
esphome/components/gl_r01_i2c/* @pkejval
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gp2y1010au0f/* @zry98
|
esphome/components/gp2y1010au0f/* @zry98
|
||||||
esphome/components/gp8403/* @jesserockz @sebydocky
|
esphome/components/gp8403/* @jesserockz
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
esphome/components/gpio/one_wire/* @ssieb
|
esphome/components/gpio/one_wire/* @ssieb
|
||||||
esphome/components/gps/* @coogle @ximex
|
esphome/components/gps/* @coogle @ximex
|
||||||
@@ -202,11 +199,9 @@ esphome/components/havells_solar/* @sourabhjaiswal
|
|||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
esphome/components/hbridge/light/* @DotNetDann
|
esphome/components/hbridge/light/* @DotNetDann
|
||||||
esphome/components/hbridge/switch/* @dwmw2
|
esphome/components/hbridge/switch/* @dwmw2
|
||||||
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
|
||||||
esphome/components/he60r/* @clydebarrow
|
esphome/components/he60r/* @clydebarrow
|
||||||
esphome/components/heatpumpir/* @rob-deutsch
|
esphome/components/heatpumpir/* @rob-deutsch
|
||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
esphome/components/hlk_fm22x/* @OnFreund
|
|
||||||
esphome/components/hm3301/* @freekode
|
esphome/components/hm3301/* @freekode
|
||||||
esphome/components/hmac_md5/* @dwmw2
|
esphome/components/hmac_md5/* @dwmw2
|
||||||
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||||
@@ -291,7 +286,6 @@ esphome/components/mcp23x17_base/* @jesserockz
|
|||||||
esphome/components/mcp23xxx_base/* @jesserockz
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||||
esphome/components/mcp3204/* @rsumner
|
esphome/components/mcp3204/* @rsumner
|
||||||
esphome/components/mcp3221/* @philippderdiedas
|
|
||||||
esphome/components/mcp4461/* @p1ngb4ck
|
esphome/components/mcp4461/* @p1ngb4ck
|
||||||
esphome/components/mcp4728/* @berfenger
|
esphome/components/mcp4728/* @berfenger
|
||||||
esphome/components/mcp47a1/* @jesserockz
|
esphome/components/mcp47a1/* @jesserockz
|
||||||
@@ -396,7 +390,6 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow
|
|||||||
esphome/components/rtl87xx/* @kuba2k2
|
esphome/components/rtl87xx/* @kuba2k2
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/runtime_stats/* @bdraco
|
esphome/components/runtime_stats/* @bdraco
|
||||||
esphome/components/rx8130/* @beormund
|
|
||||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||||
esphome/components/scd4x/* @martgras @sjtrny
|
esphome/components/scd4x/* @martgras @sjtrny
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
@@ -483,7 +476,6 @@ esphome/components/template/fan/* @ssieb
|
|||||||
esphome/components/text/* @mauritskorse
|
esphome/components/text/* @mauritskorse
|
||||||
esphome/components/thermostat/* @kbx81
|
esphome/components/thermostat/* @kbx81
|
||||||
esphome/components/time/* @esphome/core
|
esphome/components/time/* @esphome/core
|
||||||
esphome/components/tinyusb/* @kbx81
|
|
||||||
esphome/components/tlc5947/* @rnauber
|
esphome/components/tlc5947/* @rnauber
|
||||||
esphome/components/tlc5971/* @IJIJI
|
esphome/components/tlc5971/* @IJIJI
|
||||||
esphome/components/tm1621/* @Philippe12
|
esphome/components/tm1621/* @Philippe12
|
||||||
|
|||||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.11.0b2
|
PROJECT_NUMBER = 2025.11.0-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -62,40 +62,6 @@ from esphome.util import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Special non-component keys that appear in configs
|
|
||||||
_NON_COMPONENT_KEYS = frozenset(
|
|
||||||
{
|
|
||||||
CONF_ESPHOME,
|
|
||||||
"substitutions",
|
|
||||||
"packages",
|
|
||||||
"globals",
|
|
||||||
"external_components",
|
|
||||||
"<<",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def detect_external_components(config: ConfigType) -> set[str]:
|
|
||||||
"""Detect external/custom components in the configuration.
|
|
||||||
|
|
||||||
External components are those that appear in the config but are not
|
|
||||||
part of ESPHome's built-in components and are not special config keys.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: The ESPHome configuration dictionary
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A set of external component names
|
|
||||||
"""
|
|
||||||
from esphome.analyze_memory.helpers import get_esphome_components
|
|
||||||
|
|
||||||
builtin_components = get_esphome_components()
|
|
||||||
return {
|
|
||||||
key
|
|
||||||
for key in config
|
|
||||||
if key not in builtin_components and key not in _NON_COMPONENT_KEYS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArgsProtocol(Protocol):
|
class ArgsProtocol(Protocol):
|
||||||
device: list[str] | None
|
device: list[str] | None
|
||||||
@@ -207,21 +173,19 @@ def choose_upload_log_host(
|
|||||||
if has_mqtt_logging():
|
if has_mqtt_logging():
|
||||||
resolved.append("MQTT")
|
resolved.append("MQTT")
|
||||||
|
|
||||||
if has_api() and has_non_ip_address() and has_resolvable_address():
|
if has_api() and has_non_ip_address():
|
||||||
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||||
|
|
||||||
elif purpose == Purpose.UPLOADING:
|
elif purpose == Purpose.UPLOADING:
|
||||||
if has_ota() and has_mqtt_ip_lookup():
|
if has_ota() and has_mqtt_ip_lookup():
|
||||||
resolved.append("MQTTIP")
|
resolved.append("MQTTIP")
|
||||||
|
|
||||||
if has_ota() and has_non_ip_address() and has_resolvable_address():
|
if has_ota() and has_non_ip_address():
|
||||||
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||||
else:
|
else:
|
||||||
resolved.append(device)
|
resolved.append(device)
|
||||||
if not resolved:
|
if not resolved:
|
||||||
raise EsphomeError(
|
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
|
||||||
f"All specified devices {defaults} could not be resolved. Is the device connected to the network?"
|
|
||||||
)
|
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
# No devices specified, show interactive chooser
|
# No devices specified, show interactive chooser
|
||||||
@@ -318,17 +282,7 @@ def has_resolvable_address() -> bool:
|
|||||||
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||||
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
||||||
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
||||||
if CORE.address is None:
|
return CORE.address is not None
|
||||||
return False
|
|
||||||
|
|
||||||
if has_ip_address():
|
|
||||||
return True
|
|
||||||
|
|
||||||
if has_mdns():
|
|
||||||
return True
|
|
||||||
|
|
||||||
# .local mDNS hostnames are only resolvable if mDNS is enabled
|
|
||||||
return not CORE.address.endswith(".local")
|
|
||||||
|
|
||||||
|
|
||||||
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
||||||
@@ -936,54 +890,6 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
|
||||||
"""Analyze memory usage by component.
|
|
||||||
|
|
||||||
This command compiles the configuration and performs memory analysis.
|
|
||||||
Compilation is fast if sources haven't changed (just relinking).
|
|
||||||
"""
|
|
||||||
from esphome import platformio_api
|
|
||||||
from esphome.analyze_memory.cli import MemoryAnalyzerCLI
|
|
||||||
|
|
||||||
# Always compile to ensure fresh data (fast if no changes - just relinks)
|
|
||||||
exit_code = write_cpp(config)
|
|
||||||
if exit_code != 0:
|
|
||||||
return exit_code
|
|
||||||
exit_code = compile_program(args, config)
|
|
||||||
if exit_code != 0:
|
|
||||||
return exit_code
|
|
||||||
_LOGGER.info("Successfully compiled program.")
|
|
||||||
|
|
||||||
# Get idedata for analysis
|
|
||||||
idedata = platformio_api.get_idedata(config)
|
|
||||||
if idedata is None:
|
|
||||||
_LOGGER.error("Failed to get IDE data for memory analysis")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
firmware_elf = Path(idedata.firmware_elf_path)
|
|
||||||
|
|
||||||
# Extract external components from config
|
|
||||||
external_components = detect_external_components(config)
|
|
||||||
_LOGGER.debug("Detected external components: %s", external_components)
|
|
||||||
|
|
||||||
# Perform memory analysis
|
|
||||||
_LOGGER.info("Analyzing memory usage...")
|
|
||||||
analyzer = MemoryAnalyzerCLI(
|
|
||||||
str(firmware_elf),
|
|
||||||
idedata.objdump_path,
|
|
||||||
idedata.readelf_path,
|
|
||||||
external_components,
|
|
||||||
)
|
|
||||||
analyzer.analyze()
|
|
||||||
|
|
||||||
# Generate and display report
|
|
||||||
report = analyzer.generate_report()
|
|
||||||
print()
|
|
||||||
print(report)
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
new_name = args.name
|
new_name = args.name
|
||||||
for c in new_name:
|
for c in new_name:
|
||||||
@@ -1099,7 +1005,6 @@ POST_CONFIG_ACTIONS = {
|
|||||||
"idedata": command_idedata,
|
"idedata": command_idedata,
|
||||||
"rename": command_rename,
|
"rename": command_rename,
|
||||||
"discover": command_discover,
|
"discover": command_discover,
|
||||||
"analyze-memory": command_analyze_memory,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SIMPLE_CONFIG_ACTIONS = [
|
SIMPLE_CONFIG_ACTIONS = [
|
||||||
@@ -1385,14 +1290,6 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_rename.add_argument("name", help="The new name for the device.", type=str)
|
parser_rename.add_argument("name", help="The new name for the device.", type=str)
|
||||||
|
|
||||||
parser_analyze_memory = subparsers.add_parser(
|
|
||||||
"analyze-memory",
|
|
||||||
help="Analyze memory usage by component.",
|
|
||||||
)
|
|
||||||
parser_analyze_memory.add_argument(
|
|
||||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Keep backward compatibility with the old command line format of
|
# Keep backward compatibility with the old command line format of
|
||||||
# esphome <config> <command>.
|
# esphome <config> <command>.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -231,22 +231,9 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
api_component = (name, mem)
|
api_component = (name, mem)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Also include wifi_stack and other important system components if they exist
|
# Combine all components to analyze: top ESPHome + all external + API if not already included
|
||||||
system_components_to_include = [
|
components_to_analyze = list(top_esphome_components) + list(
|
||||||
# Empty list - we've finished debugging symbol categorization
|
top_external_components
|
||||||
# Add component names here if you need to debug their symbols
|
|
||||||
]
|
|
||||||
system_components = [
|
|
||||||
(name, mem)
|
|
||||||
for name, mem in components
|
|
||||||
if name in system_components_to_include
|
|
||||||
]
|
|
||||||
|
|
||||||
# Combine all components to analyze: top ESPHome + all external + API if not already included + system components
|
|
||||||
components_to_analyze = (
|
|
||||||
list(top_esphome_components)
|
|
||||||
+ list(top_external_components)
|
|
||||||
+ system_components
|
|
||||||
)
|
)
|
||||||
if api_component and api_component not in components_to_analyze:
|
if api_component and api_component not in components_to_analyze:
|
||||||
components_to_analyze.append(api_component)
|
components_to_analyze.append(api_component)
|
||||||
|
|||||||
@@ -127,39 +127,40 @@ SYMBOL_PATTERNS = {
|
|||||||
"tryget_socket_unconn",
|
"tryget_socket_unconn",
|
||||||
"cs_create_ctrl_sock",
|
"cs_create_ctrl_sock",
|
||||||
"netbuf_alloc",
|
"netbuf_alloc",
|
||||||
"tcp_", # TCP protocol functions
|
|
||||||
"udp_", # UDP protocol functions
|
|
||||||
"lwip_", # LwIP stack functions
|
|
||||||
"eagle_lwip", # ESP-specific LwIP functions
|
|
||||||
"new_linkoutput", # Link output function
|
|
||||||
"acd_", # Address Conflict Detection (ACD)
|
|
||||||
"eth_", # Ethernet functions
|
|
||||||
"mac_enable_bb", # MAC baseband enable
|
|
||||||
"reassemble_and_dispatch", # Packet reassembly
|
|
||||||
],
|
],
|
||||||
# dhcp must come before libc to avoid "dhcp_select" matching "select" pattern
|
|
||||||
"dhcp": ["dhcp", "handle_dhcp"],
|
|
||||||
"ipv6_stack": ["nd6_", "ip6_", "mld6_", "icmp6_", "icmp6_input"],
|
"ipv6_stack": ["nd6_", "ip6_", "mld6_", "icmp6_", "icmp6_input"],
|
||||||
# Order matters! More specific categories must come before general ones.
|
"wifi_stack": [
|
||||||
# mdns must come before bluetooth to avoid "_mdns_disable_pcb" matching "ble_" pattern
|
"ieee80211",
|
||||||
"mdns_lib": ["mdns"],
|
"hostap",
|
||||||
# memory_mgmt must come before wifi_stack to catch mmu_hal_* symbols
|
"sta_",
|
||||||
"memory_mgmt": [
|
"ap_",
|
||||||
"mem_",
|
"scan_",
|
||||||
"memory_",
|
"wifi_",
|
||||||
"tlsf_",
|
"wpa_",
|
||||||
"memp_",
|
"wps_",
|
||||||
"pbuf_",
|
"esp_wifi",
|
||||||
"pbuf_alloc",
|
"cnx_",
|
||||||
"pbuf_copy_partial_pbuf",
|
"wpa3_",
|
||||||
"esp_mmu_map",
|
"sae_",
|
||||||
"mmu_hal_",
|
"wDev_",
|
||||||
"s_do_mapping", # Memory mapping function, not WiFi
|
"ic_",
|
||||||
"hash_map_", # Hash map data structure
|
"mac_",
|
||||||
"umm_assimilate", # UMM malloc assimilation
|
"esf_buf",
|
||||||
|
"gWpaSm",
|
||||||
|
"sm_WPA",
|
||||||
|
"eapol_",
|
||||||
|
"owe_",
|
||||||
|
"wifiLowLevelInit",
|
||||||
|
"s_do_mapping",
|
||||||
|
"gScanStruct",
|
||||||
|
"ppSearchTxframe",
|
||||||
|
"ppMapWaitTxq",
|
||||||
|
"ppFillAMPDUBar",
|
||||||
|
"ppCheckTxConnTrafficIdle",
|
||||||
|
"ppCalTkipMic",
|
||||||
],
|
],
|
||||||
# Bluetooth categories must come BEFORE wifi_stack to avoid misclassification
|
"bluetooth": ["bt_", "ble_", "l2c_", "gatt_", "gap_", "hci_", "BT_init"],
|
||||||
# Many BLE symbols contain patterns like "ble_" that would otherwise match wifi patterns
|
"wifi_bt_coex": ["coex"],
|
||||||
"bluetooth_rom": ["r_ble", "r_lld", "r_llc", "r_llm"],
|
"bluetooth_rom": ["r_ble", "r_lld", "r_llc", "r_llm"],
|
||||||
"bluedroid_bt": [
|
"bluedroid_bt": [
|
||||||
"bluedroid",
|
"bluedroid",
|
||||||
@@ -206,61 +207,6 @@ SYMBOL_PATTERNS = {
|
|||||||
"copy_extra_byte_in_db",
|
"copy_extra_byte_in_db",
|
||||||
"parse_read_local_supported_commands_response",
|
"parse_read_local_supported_commands_response",
|
||||||
],
|
],
|
||||||
"bluetooth": [
|
|
||||||
"bt_",
|
|
||||||
"_ble_", # More specific than "ble_" to avoid matching "able_", "enable_", "disable_"
|
|
||||||
"l2c_",
|
|
||||||
"l2ble_", # L2CAP for BLE
|
|
||||||
"gatt_",
|
|
||||||
"gap_",
|
|
||||||
"hci_",
|
|
||||||
"btsnd_hcic_", # Bluetooth HCI command send functions
|
|
||||||
"BT_init",
|
|
||||||
"BT_tx_", # Bluetooth transmit functions
|
|
||||||
"esp_ble_", # Catch esp_ble_* functions
|
|
||||||
],
|
|
||||||
"bluetooth_ll": [
|
|
||||||
"llm_", # Link layer manager
|
|
||||||
"llc_", # Link layer control
|
|
||||||
"lld_", # Link layer driver
|
|
||||||
"ld_acl_", # Link layer ACL (Asynchronous Connection-Oriented)
|
|
||||||
"llcp_", # Link layer control protocol
|
|
||||||
"lmp_", # Link manager protocol
|
|
||||||
],
|
|
||||||
"wifi_bt_coex": ["coex"],
|
|
||||||
"wifi_stack": [
|
|
||||||
"ieee80211",
|
|
||||||
"hostap",
|
|
||||||
"sta_",
|
|
||||||
"wifi_ap_", # More specific than "ap_" to avoid matching "cap_", "map_"
|
|
||||||
"wifi_scan_", # More specific than "scan_" to avoid matching "_scan_" in other contexts
|
|
||||||
"wifi_",
|
|
||||||
"wpa_",
|
|
||||||
"wps_",
|
|
||||||
"esp_wifi",
|
|
||||||
"cnx_",
|
|
||||||
"wpa3_",
|
|
||||||
"sae_",
|
|
||||||
"wDev_",
|
|
||||||
"ic_mac_", # More specific than "mac_" to avoid matching emac_
|
|
||||||
"esf_buf",
|
|
||||||
"gWpaSm",
|
|
||||||
"sm_WPA",
|
|
||||||
"eapol_",
|
|
||||||
"owe_",
|
|
||||||
"wifiLowLevelInit",
|
|
||||||
# Removed "s_do_mapping" - this is memory management, not WiFi
|
|
||||||
"gScanStruct",
|
|
||||||
"ppSearchTxframe",
|
|
||||||
"ppMapWaitTxq",
|
|
||||||
"ppFillAMPDUBar",
|
|
||||||
"ppCheckTxConnTrafficIdle",
|
|
||||||
"ppCalTkipMic",
|
|
||||||
"phy_force_wifi",
|
|
||||||
"phy_unforce_wifi",
|
|
||||||
"write_wifi_chan",
|
|
||||||
"wifi_track_pll",
|
|
||||||
],
|
|
||||||
"crypto_math": [
|
"crypto_math": [
|
||||||
"ecp_",
|
"ecp_",
|
||||||
"bignum_",
|
"bignum_",
|
||||||
@@ -285,36 +231,13 @@ SYMBOL_PATTERNS = {
|
|||||||
"p_256_init_curve",
|
"p_256_init_curve",
|
||||||
"shift_sub_rows",
|
"shift_sub_rows",
|
||||||
"rshift",
|
"rshift",
|
||||||
"rijndaelEncrypt", # AES Rijndael encryption
|
|
||||||
],
|
|
||||||
# System and Arduino core functions must come before libc
|
|
||||||
"esp_system": [
|
|
||||||
"system_", # ESP system functions
|
|
||||||
"postmortem_", # Postmortem reporting
|
|
||||||
],
|
|
||||||
"arduino_core": [
|
|
||||||
"pinMode",
|
|
||||||
"resetPins",
|
|
||||||
"millis",
|
|
||||||
"micros",
|
|
||||||
"delay(", # More specific - Arduino delay function with parenthesis
|
|
||||||
"delayMicroseconds",
|
|
||||||
"digitalWrite",
|
|
||||||
"digitalRead",
|
|
||||||
],
|
|
||||||
"sntp": ["sntp_", "sntp_recv"],
|
|
||||||
"scheduler": [
|
|
||||||
"run_scheduled_",
|
|
||||||
"compute_scheduled_",
|
|
||||||
"event_TaskQueue",
|
|
||||||
],
|
],
|
||||||
"hw_crypto": ["esp_aes", "esp_sha", "esp_rsa", "esp_bignum", "esp_mpi"],
|
"hw_crypto": ["esp_aes", "esp_sha", "esp_rsa", "esp_bignum", "esp_mpi"],
|
||||||
"libc": [
|
"libc": [
|
||||||
"printf",
|
"printf",
|
||||||
"scanf",
|
"scanf",
|
||||||
"malloc",
|
"malloc",
|
||||||
"_free", # More specific than "free" to match _free, __free_r, etc. but not arbitrary "free" substring
|
"free",
|
||||||
"umm_free", # UMM malloc free function
|
|
||||||
"memcpy",
|
"memcpy",
|
||||||
"memset",
|
"memset",
|
||||||
"strcpy",
|
"strcpy",
|
||||||
@@ -336,7 +259,7 @@ SYMBOL_PATTERNS = {
|
|||||||
"_setenv_r",
|
"_setenv_r",
|
||||||
"_tzset_unlocked_r",
|
"_tzset_unlocked_r",
|
||||||
"__tzcalc_limits",
|
"__tzcalc_limits",
|
||||||
"_select", # More specific than "select" to avoid matching "dhcp_select", etc.
|
"select",
|
||||||
"scalbnf",
|
"scalbnf",
|
||||||
"strtof",
|
"strtof",
|
||||||
"strtof_l",
|
"strtof_l",
|
||||||
@@ -393,24 +316,8 @@ SYMBOL_PATTERNS = {
|
|||||||
"CSWTCH$",
|
"CSWTCH$",
|
||||||
"dst$",
|
"dst$",
|
||||||
"sulp",
|
"sulp",
|
||||||
"_strtol_l", # String to long with locale
|
|
||||||
"__cvt", # Convert
|
|
||||||
"__utoa", # Unsigned to ASCII
|
|
||||||
"__global_locale", # Global locale
|
|
||||||
"_ctype_", # Character type
|
|
||||||
"impure_data", # Impure data
|
|
||||||
],
|
|
||||||
"string_ops": [
|
|
||||||
"strcmp",
|
|
||||||
"strncmp",
|
|
||||||
"strchr",
|
|
||||||
"strstr",
|
|
||||||
"strtok",
|
|
||||||
"strdup",
|
|
||||||
"strncasecmp_P", # String compare (case insensitive, from program memory)
|
|
||||||
"strnlen_P", # String length (from program memory)
|
|
||||||
"strncat_P", # String concatenate (from program memory)
|
|
||||||
],
|
],
|
||||||
|
"string_ops": ["strcmp", "strncmp", "strchr", "strstr", "strtok", "strdup"],
|
||||||
"memory_alloc": ["malloc", "calloc", "realloc", "free", "_sbrk"],
|
"memory_alloc": ["malloc", "calloc", "realloc", "free", "_sbrk"],
|
||||||
"file_io": [
|
"file_io": [
|
||||||
"fread",
|
"fread",
|
||||||
@@ -431,26 +338,10 @@ SYMBOL_PATTERNS = {
|
|||||||
"vsscanf",
|
"vsscanf",
|
||||||
],
|
],
|
||||||
"cpp_anonymous": ["_GLOBAL__N_", "n$"],
|
"cpp_anonymous": ["_GLOBAL__N_", "n$"],
|
||||||
# Plain C patterns only - C++ symbols will be categorized via DEMANGLED_PATTERNS
|
"cpp_runtime": ["__cxx", "_ZN", "_ZL", "_ZSt", "__gxx_personality", "_Z16"],
|
||||||
"nvs": ["nvs_"], # Plain C NVS functions
|
"exception_handling": ["__cxa_", "_Unwind_", "__gcc_personality", "uw_frame_state"],
|
||||||
"ota": ["ota_", "OTA", "esp_ota", "app_desc"],
|
|
||||||
# cpp_runtime: Removed _ZN, _ZL to let DEMANGLED_PATTERNS categorize C++ symbols properly
|
|
||||||
# Only keep patterns that are truly runtime-specific and not categorizable by namespace
|
|
||||||
"cpp_runtime": ["__cxx", "_ZSt", "__gxx_personality", "_Z16"],
|
|
||||||
"exception_handling": [
|
|
||||||
"__cxa_",
|
|
||||||
"_Unwind_",
|
|
||||||
"__gcc_personality",
|
|
||||||
"uw_frame_state",
|
|
||||||
"search_object", # Search for exception handling object
|
|
||||||
"get_cie_encoding", # Get CIE encoding
|
|
||||||
"add_fdes", # Add frame description entries
|
|
||||||
"fde_unencoded_compare", # Compare FDEs
|
|
||||||
"fde_mixed_encoding_compare", # Compare mixed encoding FDEs
|
|
||||||
"frame_downheap", # Frame heap operations
|
|
||||||
"frame_heapsort", # Frame heap sorting
|
|
||||||
],
|
|
||||||
"static_init": ["_GLOBAL__sub_I_"],
|
"static_init": ["_GLOBAL__sub_I_"],
|
||||||
|
"mdns_lib": ["mdns"],
|
||||||
"phy_radio": [
|
"phy_radio": [
|
||||||
"phy_",
|
"phy_",
|
||||||
"rf_",
|
"rf_",
|
||||||
@@ -503,47 +394,10 @@ SYMBOL_PATTERNS = {
|
|||||||
"txcal_debuge_mode",
|
"txcal_debuge_mode",
|
||||||
"ant_wifitx_cfg",
|
"ant_wifitx_cfg",
|
||||||
"reg_init_begin",
|
"reg_init_begin",
|
||||||
"tx_cap_init", # TX capacitance init
|
|
||||||
"ram_set_txcap", # RAM TX capacitance setting
|
|
||||||
"tx_atten_", # TX attenuation
|
|
||||||
"txiq_", # TX I/Q calibration
|
|
||||||
"ram_cal_", # RAM calibration
|
|
||||||
"ram_rxiq_", # RAM RX I/Q
|
|
||||||
"readvdd33", # Read VDD33
|
|
||||||
"test_tout", # Test timeout
|
|
||||||
"tsen_meas", # Temperature sensor measurement
|
|
||||||
"bbpll_cal", # Baseband PLL calibration
|
|
||||||
"set_cal_", # Set calibration
|
|
||||||
"set_rfanagain_", # Set RF analog gain
|
|
||||||
"set_txdc_", # Set TX DC
|
|
||||||
"get_vdd33_", # Get VDD33
|
|
||||||
"gen_rx_gain_table", # Generate RX gain table
|
|
||||||
"ram_ana_inf_gating_en", # RAM analog interface gating enable
|
|
||||||
"tx_cont_en", # TX continuous enable
|
|
||||||
"tx_delay_cfg", # TX delay configuration
|
|
||||||
"tx_gain_table_set", # TX gain table set
|
|
||||||
"check_and_reset_hw_deadlock", # Hardware deadlock check
|
|
||||||
"s_config", # System/hardware config
|
|
||||||
"chan14_mic_cfg", # Channel 14 MIC config
|
|
||||||
],
|
|
||||||
"wifi_phy_pp": [
|
|
||||||
"pp_",
|
|
||||||
"ppT",
|
|
||||||
"ppR",
|
|
||||||
"ppP",
|
|
||||||
"ppInstall",
|
|
||||||
"ppCalTxAMPDULength",
|
|
||||||
"ppCheckTx", # Packet processor TX check
|
|
||||||
"ppCal", # Packet processor calibration
|
|
||||||
"HdlAllBuffedEb", # Handle buffered EB
|
|
||||||
],
|
],
|
||||||
|
"wifi_phy_pp": ["pp_", "ppT", "ppR", "ppP", "ppInstall", "ppCalTxAMPDULength"],
|
||||||
"wifi_lmac": ["lmac"],
|
"wifi_lmac": ["lmac"],
|
||||||
"wifi_device": [
|
"wifi_device": ["wdev", "wDev_"],
|
||||||
"wdev",
|
|
||||||
"wDev_",
|
|
||||||
"ic_set_sta", # Set station mode
|
|
||||||
"ic_set_vif", # Set virtual interface
|
|
||||||
],
|
|
||||||
"power_mgmt": [
|
"power_mgmt": [
|
||||||
"pm_",
|
"pm_",
|
||||||
"sleep",
|
"sleep",
|
||||||
@@ -552,7 +406,15 @@ SYMBOL_PATTERNS = {
|
|||||||
"deep_sleep",
|
"deep_sleep",
|
||||||
"power_down",
|
"power_down",
|
||||||
"g_pm",
|
"g_pm",
|
||||||
"pmc", # Power Management Controller
|
],
|
||||||
|
"memory_mgmt": [
|
||||||
|
"mem_",
|
||||||
|
"memory_",
|
||||||
|
"tlsf_",
|
||||||
|
"memp_",
|
||||||
|
"pbuf_",
|
||||||
|
"pbuf_alloc",
|
||||||
|
"pbuf_copy_partial_pbuf",
|
||||||
],
|
],
|
||||||
"hal_layer": ["hal_"],
|
"hal_layer": ["hal_"],
|
||||||
"clock_mgmt": [
|
"clock_mgmt": [
|
||||||
@@ -577,6 +439,7 @@ SYMBOL_PATTERNS = {
|
|||||||
"error_handling": ["panic", "abort", "assert", "error_", "fault"],
|
"error_handling": ["panic", "abort", "assert", "error_", "fault"],
|
||||||
"authentication": ["auth"],
|
"authentication": ["auth"],
|
||||||
"ppp_protocol": ["ppp", "ipcp_", "lcp_", "chap_", "LcpEchoCheck"],
|
"ppp_protocol": ["ppp", "ipcp_", "lcp_", "chap_", "LcpEchoCheck"],
|
||||||
|
"dhcp": ["dhcp", "handle_dhcp"],
|
||||||
"ethernet_phy": [
|
"ethernet_phy": [
|
||||||
"emac_",
|
"emac_",
|
||||||
"eth_phy_",
|
"eth_phy_",
|
||||||
@@ -755,15 +618,7 @@ SYMBOL_PATTERNS = {
|
|||||||
"ampdu_dispatch_upto",
|
"ampdu_dispatch_upto",
|
||||||
],
|
],
|
||||||
"ieee802_11": ["ieee802_11_", "ieee802_11_parse_elems"],
|
"ieee802_11": ["ieee802_11_", "ieee802_11_parse_elems"],
|
||||||
"rate_control": [
|
"rate_control": ["rssi_margin", "rcGetSched", "get_rate_fcc_index"],
|
||||||
"rssi_margin",
|
|
||||||
"rcGetSched",
|
|
||||||
"get_rate_fcc_index",
|
|
||||||
"rcGetRate", # Get rate
|
|
||||||
"rc_get_", # Rate control getters
|
|
||||||
"rc_set_", # Rate control setters
|
|
||||||
"rc_enable_", # Rate control enable functions
|
|
||||||
],
|
|
||||||
"nan": ["nan_dp_", "nan_dp_post_tx", "nan_dp_delete_peer"],
|
"nan": ["nan_dp_", "nan_dp_post_tx", "nan_dp_delete_peer"],
|
||||||
"channel_mgmt": ["chm_init", "chm_set_current_channel"],
|
"channel_mgmt": ["chm_init", "chm_set_current_channel"],
|
||||||
"trace": ["trc_init", "trc_onAmpduOp"],
|
"trace": ["trc_init", "trc_onAmpduOp"],
|
||||||
@@ -944,18 +799,31 @@ SYMBOL_PATTERNS = {
|
|||||||
"supports_interlaced_inquiry_scan",
|
"supports_interlaced_inquiry_scan",
|
||||||
"supports_reading_remote_extended_features",
|
"supports_reading_remote_extended_features",
|
||||||
],
|
],
|
||||||
|
"bluetooth_ll": [
|
||||||
|
"lld_pdu_",
|
||||||
|
"ld_acl_",
|
||||||
|
"lld_stop_ind_handler",
|
||||||
|
"lld_evt_winsize_change",
|
||||||
|
"config_lld_evt_funcs_reset",
|
||||||
|
"config_lld_funcs_reset",
|
||||||
|
"config_llm_funcs_reset",
|
||||||
|
"llm_set_long_adv_data",
|
||||||
|
"lld_retry_tx_prog",
|
||||||
|
"llc_link_sup_to_ind_handler",
|
||||||
|
"config_llc_funcs_reset",
|
||||||
|
"lld_evt_rxwin_compute",
|
||||||
|
"config_btdm_funcs_reset",
|
||||||
|
"config_ea_funcs_reset",
|
||||||
|
"llc_defalut_state_tab_reset",
|
||||||
|
"config_rwip_funcs_reset",
|
||||||
|
"ke_lmp_rx_flooding_detect",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Demangled patterns: patterns found in demangled C++ names
|
# Demangled patterns: patterns found in demangled C++ names
|
||||||
DEMANGLED_PATTERNS = {
|
DEMANGLED_PATTERNS = {
|
||||||
"gpio_driver": ["GPIO"],
|
"gpio_driver": ["GPIO"],
|
||||||
"uart_driver": ["UART"],
|
"uart_driver": ["UART"],
|
||||||
# mdns_lib must come before network_stack to avoid "udp" matching "_udpReadBuffer" in MDNSResponder
|
|
||||||
"mdns_lib": [
|
|
||||||
"MDNSResponder",
|
|
||||||
"MDNSImplementation",
|
|
||||||
"MDNS",
|
|
||||||
],
|
|
||||||
"network_stack": [
|
"network_stack": [
|
||||||
"lwip",
|
"lwip",
|
||||||
"tcp",
|
"tcp",
|
||||||
@@ -968,24 +836,6 @@ DEMANGLED_PATTERNS = {
|
|||||||
"ethernet",
|
"ethernet",
|
||||||
"ppp",
|
"ppp",
|
||||||
"slip",
|
"slip",
|
||||||
"UdpContext", # UDP context class
|
|
||||||
"DhcpServer", # DHCP server class
|
|
||||||
],
|
|
||||||
"arduino_core": [
|
|
||||||
"String::", # Arduino String class
|
|
||||||
"Print::", # Arduino Print class
|
|
||||||
"HardwareSerial::", # Serial class
|
|
||||||
"IPAddress::", # IP address class
|
|
||||||
"EspClass::", # ESP class
|
|
||||||
"experimental::_SPI", # Experimental SPI
|
|
||||||
],
|
|
||||||
"ota": [
|
|
||||||
"UpdaterClass",
|
|
||||||
"Updater::",
|
|
||||||
],
|
|
||||||
"wifi": [
|
|
||||||
"ESP8266WiFi",
|
|
||||||
"WiFi::",
|
|
||||||
],
|
],
|
||||||
"wifi_stack": ["NetworkInterface"],
|
"wifi_stack": ["NetworkInterface"],
|
||||||
"nimble_bt": [
|
"nimble_bt": [
|
||||||
@@ -1004,6 +854,7 @@ DEMANGLED_PATTERNS = {
|
|||||||
"rtti": ["__type_info", "__class_type_info"],
|
"rtti": ["__type_info", "__class_type_info"],
|
||||||
"web_server_lib": ["AsyncWebServer", "AsyncWebHandler", "WebServer"],
|
"web_server_lib": ["AsyncWebServer", "AsyncWebHandler", "WebServer"],
|
||||||
"async_tcp": ["AsyncClient", "AsyncServer"],
|
"async_tcp": ["AsyncClient", "AsyncServer"],
|
||||||
|
"mdns_lib": ["mdns"],
|
||||||
"json_lib": [
|
"json_lib": [
|
||||||
"ArduinoJson",
|
"ArduinoJson",
|
||||||
"JsonDocument",
|
"JsonDocument",
|
||||||
|
|||||||
@@ -15,13 +15,8 @@ from esphome.const import (
|
|||||||
CONF_TYPE_ID,
|
CONF_TYPE_ID,
|
||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
from esphome.core import ID, Lambda
|
from esphome.core import ID
|
||||||
from esphome.cpp_generator import (
|
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
|
||||||
LambdaExpression,
|
|
||||||
MockObj,
|
|
||||||
MockObjClass,
|
|
||||||
TemplateArgsType,
|
|
||||||
)
|
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -92,7 +87,6 @@ def validate_potentially_or_condition(value):
|
|||||||
|
|
||||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||||
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
|
|
||||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||||
@@ -103,40 +97,9 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
|||||||
Automation = cg.esphome_ns.class_("Automation")
|
Automation = cg.esphome_ns.class_("Automation")
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||||
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
|
|
||||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
def new_lambda_pvariable(
|
|
||||||
id_obj: ID,
|
|
||||||
lambda_expr: LambdaExpression,
|
|
||||||
stateless_class: MockObjClass,
|
|
||||||
template_arg: cg.TemplateArguments | None = None,
|
|
||||||
) -> MockObj:
|
|
||||||
"""Create Pvariable for lambda, using stateless class if applicable.
|
|
||||||
|
|
||||||
Combines ID selection and Pvariable creation in one call. For stateless
|
|
||||||
lambdas (empty capture), uses function pointer instead of std::function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id_obj: The ID object (action_id, condition_id, or filter_id)
|
|
||||||
lambda_expr: The lambda expression object
|
|
||||||
stateless_class: The stateless class to use for stateless lambdas
|
|
||||||
template_arg: Optional template arguments (for actions/conditions)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The created Pvariable
|
|
||||||
"""
|
|
||||||
# For stateless lambdas, use function pointer instead of std::function
|
|
||||||
if lambda_expr.capture == "":
|
|
||||||
id_obj = id_obj.copy()
|
|
||||||
id_obj.type = stateless_class
|
|
||||||
|
|
||||||
if template_arg is not None:
|
|
||||||
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
|
|
||||||
return cg.new_Pvariable(id_obj, lambda_expr)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
if extra_schema is None:
|
if extra_schema is None:
|
||||||
extra_schema = {}
|
extra_schema = {}
|
||||||
@@ -182,7 +145,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
value = cv.Schema([extra_validators])(value)
|
value = cv.Schema([extra_validators])(value)
|
||||||
if single:
|
if single:
|
||||||
if len(value) != 1:
|
if len(value) != 1:
|
||||||
raise cv.Invalid("This trigger allows only a single automation")
|
raise cv.Invalid("Cannot have more than 1 automation for templates")
|
||||||
return value[0]
|
return value[0]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -277,9 +240,7 @@ async def lambda_condition_to_code(
|
|||||||
args: TemplateArgsType,
|
args: TemplateArgsType,
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||||
return new_lambda_pvariable(
|
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||||
condition_id, lambda_, StatelessLambdaCondition, template_arg
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@register_condition(
|
@register_condition(
|
||||||
@@ -310,30 +271,6 @@ async def for_condition_to_code(
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_condition(
|
|
||||||
"component.is_idle",
|
|
||||||
LambdaCondition,
|
|
||||||
maybe_simple_id(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_ID): cv.use_id(cg.Component),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
async def component_is_idle_condition_to_code(
|
|
||||||
config: ConfigType,
|
|
||||||
condition_id: ID,
|
|
||||||
template_arg: cg.TemplateArguments,
|
|
||||||
args: TemplateArgsType,
|
|
||||||
) -> MockObj:
|
|
||||||
comp = await cg.get_variable(config[CONF_ID])
|
|
||||||
lambda_ = await cg.process_lambda(
|
|
||||||
Lambda(f"return {comp}->is_idle();"), args, return_type=bool
|
|
||||||
)
|
|
||||||
return new_lambda_pvariable(
|
|
||||||
condition_id, lambda_, StatelessLambdaCondition, template_arg
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@register_action(
|
@register_action(
|
||||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||||
)
|
)
|
||||||
@@ -469,7 +406,7 @@ async def lambda_action_to_code(
|
|||||||
args: TemplateArgsType,
|
args: TemplateArgsType,
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||||
return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
|
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_action(
|
@register_action(
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ from esphome.cpp_types import ( # noqa: F401
|
|||||||
EntityBase,
|
EntityBase,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
ESPTime,
|
ESPTime,
|
||||||
FixedVector,
|
|
||||||
GPIOPin,
|
GPIOPin,
|
||||||
InternalGPIOPin,
|
InternalGPIOPin,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ static const char *const TAG = "adalight_light_effect";
|
|||||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||||
|
|
||||||
AdalightLightEffect::AdalightLightEffect(const char *name) : AddressableLightEffect(name) {}
|
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||||
|
|
||||||
void AdalightLightEffect::start() {
|
void AdalightLightEffect::start() {
|
||||||
AddressableLightEffect::start();
|
AddressableLightEffect::start();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace adalight {
|
|||||||
|
|
||||||
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
AdalightLightEffect(const char *name);
|
AdalightLightEffect(const std::string &name);
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>,
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint8_t, new_address)
|
TEMPLATABLE_VALUE(uint8_t, new_address)
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); }
|
void play(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AGS10SetZeroPointActionMode {
|
enum AGS10SetZeroPointActionMode {
|
||||||
@@ -122,7 +122,7 @@ template<typename... Ts> class AGS10SetZeroPointAction : public Action<Ts...>, p
|
|||||||
TEMPLATABLE_VALUE(uint16_t, value)
|
TEMPLATABLE_VALUE(uint16_t, value)
|
||||||
TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode)
|
TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
switch (this->mode_.value(x...)) {
|
switch (this->mode_.value(x...)) {
|
||||||
case FACTORY_DEFAULT:
|
case FACTORY_DEFAULT:
|
||||||
this->parent_->set_zero_point_with_factory_defaults();
|
this->parent_->set_zero_point_with_factory_defaults();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
|
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AIC3204 *aic3204_;
|
AIC3204 *aic3204_;
|
||||||
|
|||||||
@@ -172,6 +172,12 @@ def alarm_control_panel_schema(
|
|||||||
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
|
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
|
||||||
|
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
|
||||||
|
cv.deprecated_schema_constant("alarm_control_panel")
|
||||||
|
)
|
||||||
|
|
||||||
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
|
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.use_id(AlarmControlPanel),
|
cv.GenerateID(): cv.use_id(AlarmControlPanel),
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#include "alarm_control_panel.h"
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "alarm_control_panel.h"
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.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)));
|
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||||
this->current_state_ = state;
|
this->current_state_ = state;
|
||||||
this->state_callback_.call();
|
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) {
|
if (state == ACP_STATE_TRIGGERED) {
|
||||||
this->triggered_callback_.call();
|
this->triggered_callback_.call();
|
||||||
} else if (state == ACP_STATE_ARMING) {
|
} else if (state == ACP_STATE_ARMING) {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
auto call = this->alarm_control_panel_->make_call();
|
||||||
auto code = this->code_.optional_value(x...);
|
auto code = this->code_.optional_value(x...);
|
||||||
if (code.has_value()) {
|
if (code.has_value()) {
|
||||||
@@ -109,7 +109,7 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
auto call = this->alarm_control_panel_->make_call();
|
||||||
auto code = this->code_.optional_value(x...);
|
auto code = this->code_.optional_value(x...);
|
||||||
if (code.has_value()) {
|
if (code.has_value()) {
|
||||||
@@ -129,7 +129,7 @@ template<typename... Ts> class ArmNightAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
auto call = this->alarm_control_panel_->make_call();
|
||||||
auto code = this->code_.optional_value(x...);
|
auto code = this->code_.optional_value(x...);
|
||||||
if (code.has_value()) {
|
if (code.has_value()) {
|
||||||
@@ -149,7 +149,7 @@ template<typename... Ts> class DisarmAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
|
void play(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
@@ -159,7 +159,7 @@ template<typename... Ts> class PendingAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->alarm_control_panel_->make_call().pending().perform(); }
|
void play(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
@@ -169,7 +169,7 @@ template<typename... Ts> class TriggeredAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
|
void play(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AlarmControlPanel *alarm_control_panel_;
|
AlarmControlPanel *alarm_control_panel_;
|
||||||
@@ -178,7 +178,7 @@ template<typename... Ts> class TriggeredAction : public Action<Ts...> {
|
|||||||
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
|
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
|
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
|
||||||
bool check(const Ts &...x) override {
|
bool check(Ts... x) override {
|
||||||
return this->parent_->is_state_armed(this->parent_->get_state()) ||
|
return this->parent_->is_state_armed(this->parent_->get_state()) ||
|
||||||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
|
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_TRIGGERED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Animation : public image::Image {
|
|||||||
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
|
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
|
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
|
||||||
void play(const Ts &...x) override { this->parent_->next_frame(); }
|
void play(Ts... x) override { this->parent_->next_frame(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Animation *parent_;
|
Animation *parent_;
|
||||||
@@ -48,7 +48,7 @@ template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
|
|||||||
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
|
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
|
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
|
||||||
void play(const Ts &...x) override { this->parent_->prev_frame(); }
|
void play(Ts... x) override { this->parent_->prev_frame(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Animation *parent_;
|
Animation *parent_;
|
||||||
@@ -58,7 +58,7 @@ template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
|
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(uint16_t, frame)
|
TEMPLATABLE_VALUE(uint16_t, frame)
|
||||||
void play(const Ts &...x) override { this->parent_->set_frame(this->frame_.value(x...)); }
|
void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Animation *parent_;
|
Animation *parent_;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
climate::ClimateTraits traits() override {
|
climate::ClimateTraits traits() override {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
traits.set_supports_current_temperature(true);
|
||||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
|
||||||
traits.set_visual_min_temperature(25.0);
|
traits.set_visual_min_temperature(25.0);
|
||||||
traits.set_visual_max_temperature(100.0);
|
traits.set_visual_max_temperature(100.0);
|
||||||
|
|||||||
@@ -71,12 +71,10 @@ SERVICE_ARG_NATIVE_TYPES = {
|
|||||||
"int": cg.int32,
|
"int": cg.int32,
|
||||||
"float": float,
|
"float": float,
|
||||||
"string": cg.std_string,
|
"string": cg.std_string,
|
||||||
"bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
|
"bool[]": cg.std_vector.template(bool),
|
||||||
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
"int[]": cg.std_vector.template(cg.int32),
|
||||||
"float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
|
"float[]": cg.std_vector.template(float),
|
||||||
"string[]": cg.FixedVector.template(cg.std_string)
|
"string[]": cg.std_vector.template(cg.std_string),
|
||||||
.operator("const")
|
|
||||||
.operator("ref"),
|
|
||||||
}
|
}
|
||||||
CONF_ENCRYPTION = "encryption"
|
CONF_ENCRYPTION = "encryption"
|
||||||
CONF_BATCH_DELAY = "batch_delay"
|
CONF_BATCH_DELAY = "batch_delay"
|
||||||
@@ -157,17 +155,6 @@ def _validate_api_config(config: ConfigType) -> ConfigType:
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def _consume_api_sockets(config: ConfigType) -> ConfigType:
|
|
||||||
"""Register socket needs for API component."""
|
|
||||||
from esphome.components import socket
|
|
||||||
|
|
||||||
# API needs 1 listening socket + typically 3 concurrent client connections
|
|
||||||
# (not max_connections, which is the upper limit rarely reached)
|
|
||||||
sockets_needed = 1 + 3
|
|
||||||
socket.consume_sockets(sockets_needed, "api")(config)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -227,7 +214,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
esp32=8, # More RAM, can buffer more
|
esp32=8, # More RAM, can buffer more
|
||||||
rp2040=5, # Limited RAM
|
rp2040=5, # Limited RAM
|
||||||
bk72xx=8, # Moderate RAM
|
bk72xx=8, # Moderate RAM
|
||||||
nrf52=8, # Moderate RAM
|
|
||||||
rtl87xx=8, # Moderate RAM
|
rtl87xx=8, # Moderate RAM
|
||||||
host=16, # Abundant resources
|
host=16, # Abundant resources
|
||||||
ln882x=8, # Moderate RAM
|
ln882x=8, # Moderate RAM
|
||||||
@@ -236,7 +222,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
||||||
_validate_api_config,
|
_validate_api_config,
|
||||||
_consume_api_sockets,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -245,9 +230,6 @@ async def to_code(config):
|
|||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
# Track controller registration for StaticVector sizing
|
|
||||||
CORE.register_controller()
|
|
||||||
|
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(var.set_port(config[CONF_PORT]))
|
||||||
if config[CONF_PASSWORD]:
|
if config[CONF_PASSWORD]:
|
||||||
cg.add_define("USE_API_PASSWORD")
|
cg.add_define("USE_API_PASSWORD")
|
||||||
@@ -264,10 +246,6 @@ async def to_code(config):
|
|||||||
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||||
cg.add_define("USE_API_SERVICES")
|
cg.add_define("USE_API_SERVICES")
|
||||||
|
|
||||||
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
|
|
||||||
if config[CONF_CUSTOM_SERVICES]:
|
|
||||||
cg.add_define("USE_API_CUSTOM_SERVICES")
|
|
||||||
|
|
||||||
if config[CONF_HOMEASSISTANT_SERVICES]:
|
if config[CONF_HOMEASSISTANT_SERVICES]:
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
|
|
||||||
@@ -275,8 +253,6 @@ async def to_code(config):
|
|||||||
cg.add_define("USE_API_HOMEASSISTANT_STATES")
|
cg.add_define("USE_API_HOMEASSISTANT_STATES")
|
||||||
|
|
||||||
if actions := config.get(CONF_ACTIONS, []):
|
if actions := config.get(CONF_ACTIONS, []):
|
||||||
# Collect all triggers first, then register all at once with initializer_list
|
|
||||||
triggers: list[cg.Pvariable] = []
|
|
||||||
for conf in actions:
|
for conf in actions:
|
||||||
template_args = []
|
template_args = []
|
||||||
func_args = []
|
func_args = []
|
||||||
@@ -290,10 +266,8 @@ async def to_code(config):
|
|||||||
trigger = cg.new_Pvariable(
|
trigger = cg.new_Pvariable(
|
||||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||||
)
|
)
|
||||||
triggers.append(trigger)
|
cg.add(var.register_user_service(trigger))
|
||||||
await automation.build_automation(trigger, func_args, conf)
|
await automation.build_automation(trigger, func_args, conf)
|
||||||
# Register all services at once - single allocation, no reallocations
|
|
||||||
cg.add(var.initialize_user_services(triggers))
|
|
||||||
|
|
||||||
if CONF_ON_CLIENT_CONNECTED in config:
|
if CONF_ON_CLIENT_CONNECTED in config:
|
||||||
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
|
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ message ListEntitiesFanResponse {
|
|||||||
bool disabled_by_default = 9;
|
bool disabled_by_default = 9;
|
||||||
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
|
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||||
EntityCategory entity_category = 11;
|
EntityCategory entity_category = 11;
|
||||||
repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "std::vector<const char *>"];
|
repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"];
|
||||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
// Deprecated in API version 1.6 - only used in deprecated fields
|
// Deprecated in API version 1.6 - only used in deprecated fields
|
||||||
@@ -506,7 +506,7 @@ message ListEntitiesLightResponse {
|
|||||||
string name = 3;
|
string name = 3;
|
||||||
reserved 4; // Deprecated: was string unique_id
|
reserved 4; // Deprecated: was string unique_id
|
||||||
|
|
||||||
repeated ColorMode supported_color_modes = 12 [(container_pointer_no_template) = "light::ColorModeMask"];
|
repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"];
|
||||||
// next four supports_* are for legacy clients, newer clients should use color modes
|
// next four supports_* are for legacy clients, newer clients should use color modes
|
||||||
// Deprecated in API version 1.6
|
// Deprecated in API version 1.6
|
||||||
bool legacy_supports_brightness = 5 [deprecated=true];
|
bool legacy_supports_brightness = 5 [deprecated=true];
|
||||||
@@ -989,7 +989,7 @@ message ListEntitiesClimateResponse {
|
|||||||
|
|
||||||
bool supports_current_temperature = 5; // Deprecated: use feature_flags
|
bool supports_current_temperature = 5; // Deprecated: use feature_flags
|
||||||
bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags
|
bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags
|
||||||
repeated ClimateMode supported_modes = 7 [(container_pointer_no_template) = "climate::ClimateModeMask"];
|
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
|
||||||
float visual_min_temperature = 8;
|
float visual_min_temperature = 8;
|
||||||
float visual_max_temperature = 9;
|
float visual_max_temperature = 9;
|
||||||
float visual_target_temperature_step = 10;
|
float visual_target_temperature_step = 10;
|
||||||
@@ -998,11 +998,11 @@ message ListEntitiesClimateResponse {
|
|||||||
// Deprecated in API version 1.5
|
// Deprecated in API version 1.5
|
||||||
bool legacy_supports_away = 11 [deprecated=true];
|
bool legacy_supports_away = 11 [deprecated=true];
|
||||||
bool supports_action = 12; // Deprecated: use feature_flags
|
bool supports_action = 12; // Deprecated: use feature_flags
|
||||||
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"];
|
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
|
||||||
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
|
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
|
||||||
repeated string supported_custom_fan_modes = 15 [(container_pointer_no_template) = "std::vector<const char *>"];
|
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
|
||||||
repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
|
repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"];
|
||||||
repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "std::vector<const char *>"];
|
repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"];
|
||||||
bool disabled_by_default = 18;
|
bool disabled_by_default = 18;
|
||||||
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
|
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||||
EntityCategory entity_category = 20;
|
EntityCategory entity_category = 20;
|
||||||
@@ -1143,7 +1143,7 @@ message ListEntitiesSelectResponse {
|
|||||||
reserved 4; // Deprecated: was string unique_id
|
reserved 4; // Deprecated: was string unique_id
|
||||||
|
|
||||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||||
repeated string options = 6 [(container_pointer_no_template) = "FixedVector<const char *>"];
|
repeated string options = 6 [(container_pointer) = "std::vector"];
|
||||||
bool disabled_by_default = 7;
|
bool disabled_by_default = 7;
|
||||||
EntityCategory entity_category = 8;
|
EntityCategory entity_category = 8;
|
||||||
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];
|
||||||
@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
|
|||||||
EntityCategory entity_category = 7;
|
EntityCategory entity_category = 7;
|
||||||
string device_class = 8;
|
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"];
|
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
message EventResponse {
|
message EventResponse {
|
||||||
|
|||||||
@@ -410,8 +410,8 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
|||||||
}
|
}
|
||||||
if (traits.supports_direction())
|
if (traits.supports_direction())
|
||||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||||
if (traits.supports_preset_modes() && fan->has_preset_mode())
|
if (traits.supports_preset_modes())
|
||||||
msg.set_preset_mode(StringRef(fan->get_preset_mode()));
|
msg.set_preset_mode(StringRef(fan->preset_mode));
|
||||||
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@@ -423,7 +423,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
|
|||||||
msg.supports_speed = traits.supports_speed();
|
msg.supports_speed = traits.supports_speed();
|
||||||
msg.supports_direction = traits.supports_direction();
|
msg.supports_direction = traits.supports_direction();
|
||||||
msg.supported_speed_count = traits.supported_speed_count();
|
msg.supported_speed_count = traits.supported_speed_count();
|
||||||
msg.supported_preset_modes = &traits.supported_preset_modes();
|
msg.supported_preset_modes = &traits.supported_preset_modes_for_api_();
|
||||||
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
@@ -453,6 +453,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *light = static_cast<light::LightState *>(entity);
|
auto *light = static_cast<light::LightState *>(entity);
|
||||||
LightStateResponse resp;
|
LightStateResponse resp;
|
||||||
|
auto traits = light->get_traits();
|
||||||
auto values = light->remote_values;
|
auto values = light->remote_values;
|
||||||
auto color_mode = values.get_color_mode();
|
auto color_mode = values.get_color_mode();
|
||||||
resp.state = values.is_on();
|
resp.state = values.is_on();
|
||||||
@@ -476,9 +477,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
|||||||
auto *light = static_cast<light::LightState *>(entity);
|
auto *light = static_cast<light::LightState *>(entity);
|
||||||
ListEntitiesLightResponse msg;
|
ListEntitiesLightResponse msg;
|
||||||
auto traits = light->get_traits();
|
auto traits = light->get_traits();
|
||||||
auto supported_modes = traits.get_supported_color_modes();
|
msg.supported_color_modes = &traits.get_supported_color_modes_for_api_();
|
||||||
// Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values
|
|
||||||
msg.supported_color_modes = &supported_modes;
|
|
||||||
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
||||||
msg.min_mireds = traits.get_min_mireds();
|
msg.min_mireds = traits.get_min_mireds();
|
||||||
@@ -487,7 +486,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
|||||||
if (light->supports_effects()) {
|
if (light->supports_effects()) {
|
||||||
msg.effects.emplace_back("None");
|
msg.effects.emplace_back("None");
|
||||||
for (auto *effect : light->get_effects()) {
|
for (auto *effect : light->get_effects()) {
|
||||||
msg.effects.emplace_back(effect->get_name());
|
msg.effects.push_back(effect->get_name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
@@ -638,14 +637,14 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
|||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
|
||||||
resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode()));
|
resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
|
||||||
}
|
}
|
||||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
|
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
|
||||||
resp.set_custom_preset(StringRef(climate->get_custom_preset()));
|
resp.set_custom_preset(StringRef(climate->custom_preset.value()));
|
||||||
}
|
}
|
||||||
if (traits.get_supports_swing_modes())
|
if (traits.get_supports_swing_modes())
|
||||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||||
@@ -662,26 +661,25 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
ListEntitiesClimateResponse msg;
|
ListEntitiesClimateResponse msg;
|
||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
// Flags set for backward compatibility, deprecated in 2025.11.0
|
// Flags set for backward compatibility, deprecated in 2025.11.0
|
||||||
msg.supports_current_temperature = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||||
msg.supports_current_humidity = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
msg.supports_current_humidity = traits.get_supports_current_humidity();
|
||||||
msg.supports_two_point_target_temperature = traits.has_feature_flags(
|
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||||
climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);
|
msg.supports_target_humidity = traits.get_supports_target_humidity();
|
||||||
msg.supports_target_humidity = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY);
|
msg.supports_action = traits.get_supports_action();
|
||||||
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
|
|
||||||
// Current feature flags and other supported parameters
|
// Current feature flags and other supported parameters
|
||||||
msg.feature_flags = traits.get_feature_flags();
|
msg.feature_flags = traits.get_feature_flags();
|
||||||
msg.supported_modes = &traits.get_supported_modes();
|
msg.supported_modes = &traits.get_supported_modes_for_api_();
|
||||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||||
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
||||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||||
msg.supported_fan_modes = &traits.get_supported_fan_modes();
|
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
|
||||||
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
|
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
|
||||||
msg.supported_presets = &traits.get_supported_presets();
|
msg.supported_presets = &traits.get_supported_presets_for_api_();
|
||||||
msg.supported_custom_presets = &traits.get_supported_custom_presets();
|
msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
|
||||||
msg.supported_swing_modes = &traits.get_supported_swing_modes();
|
msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
|
||||||
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@@ -878,7 +876,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *select = static_cast<select::Select *>(entity);
|
auto *select = static_cast<select::Select *>(entity);
|
||||||
SelectStateResponse resp;
|
SelectStateResponse resp;
|
||||||
resp.set_state(StringRef(select->current_option()));
|
resp.set_state(StringRef(select->state));
|
||||||
resp.missing_state = !select->has_state();
|
resp.missing_state = !select->has_state();
|
||||||
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@@ -1083,8 +1081,13 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
|||||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||||
#ifdef USE_TIME_TIMEZONE
|
#ifdef USE_TIME_TIMEZONE
|
||||||
if (value.timezone_len > 0) {
|
if (value.timezone_len > 0) {
|
||||||
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone),
|
const std::string ¤t_tz = homeassistant::global_homeassistant_time->get_timezone();
|
||||||
value.timezone_len);
|
// Compare without allocating a string
|
||||||
|
if (current_tz.length() != value.timezone_len ||
|
||||||
|
memcmp(current_tz.c_str(), value.timezone, value.timezone_len) != 0) {
|
||||||
|
homeassistant::global_homeassistant_time->set_timezone(
|
||||||
|
std::string(reinterpret_cast<const char *>(value.timezone), value.timezone_len));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1295,11 +1298,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||||
EventResponse::ESTIMATED_SIZE);
|
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) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
EventResponse resp;
|
EventResponse resp;
|
||||||
resp.set_event_type(StringRef(event_type));
|
resp.set_event_type(StringRef(event_type));
|
||||||
@@ -1311,7 +1314,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
|||||||
auto *event = static_cast<event::Event *>(entity);
|
auto *event = static_cast<event::Event *>(entity);
|
||||||
ListEntitiesEventResponse msg;
|
ListEntitiesEventResponse msg;
|
||||||
msg.set_device_class(event->get_device_class_ref());
|
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,
|
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@@ -1468,8 +1472,6 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
||||||
#elif defined(USE_LN882X)
|
#elif defined(USE_LN882X)
|
||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
|
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)
|
#elif defined(USE_RTL87XX)
|
||||||
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
||||||
#elif defined(USE_HOST)
|
#elif defined(USE_HOST)
|
||||||
@@ -1574,13 +1576,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
|||||||
resp.success = false;
|
resp.success = false;
|
||||||
|
|
||||||
psk_t psk{};
|
psk_t psk{};
|
||||||
if (msg.key.empty()) {
|
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||||
if (this->parent_->clear_noise_psk(true)) {
|
|
||||||
resp.success = true;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
|
||||||
}
|
|
||||||
} else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
|
||||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||||
@@ -1651,7 +1647,9 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
// O(n) but optimized for RAM and not performance.
|
// O(n) but optimized for RAM and not performance.
|
||||||
for (auto &item : items) {
|
for (auto &item : items) {
|
||||||
if (item.entity == entity && item.message_type == message_type) {
|
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);
|
item.creator = std::move(creator);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1821,7 +1819,7 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
// Handle remaining items more efficiently
|
// Handle remaining items more efficiently
|
||||||
if (items_processed < this->deferred_batch_.size()) {
|
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);
|
this->deferred_batch_.remove_front(items_processed);
|
||||||
// Reschedule for remaining items
|
// Reschedule for remaining items
|
||||||
this->schedule_batch_();
|
this->schedule_batch_();
|
||||||
@@ -1834,10 +1832,10 @@ void APIConnection::process_batch_() {
|
|||||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single, uint8_t message_type) const {
|
bool is_single, uint8_t message_type) const {
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
// Special case: EventResponse uses const char * pointer
|
// Special case: EventResponse uses string pointer
|
||||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||||
auto *e = static_cast<event::Event *>(entity);
|
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
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
@@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
bool is_single);
|
bool is_single);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#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);
|
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);
|
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
||||||
#endif
|
#endif
|
||||||
@@ -508,8 +508,10 @@ class APIConnection final : public APIServerConnection {
|
|||||||
// Constructor for function pointer
|
// Constructor for function pointer
|
||||||
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
||||||
|
|
||||||
// Constructor for const char * (Event types - no allocation needed)
|
// Constructor for string state capture
|
||||||
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
|
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
|
// Delete copy operations - MessageCreator should only be moved
|
||||||
MessageCreator(const MessageCreator &other) = delete;
|
MessageCreator(const MessageCreator &other) = delete;
|
||||||
@@ -521,6 +523,8 @@ class APIConnection final : public APIServerConnection {
|
|||||||
// Move assignment
|
// Move assignment
|
||||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||||
if (this != &other) {
|
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_;
|
data_ = other.data_;
|
||||||
other.data_.function_ptr = nullptr;
|
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,
|
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||||
uint8_t message_type) const;
|
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:
|
private:
|
||||||
union Data {
|
union Data {
|
||||||
MessageCreatorPtr function_ptr;
|
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
|
} 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;
|
std::vector<BatchItem> items;
|
||||||
uint32_t batch_start_time{0};
|
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() {
|
DeferredBatch() {
|
||||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||||
items.reserve(8);
|
items.reserve(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~DeferredBatch() {
|
||||||
|
// Ensure cleanup of any remaining items
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
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)
|
// 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);
|
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() {
|
void clear() {
|
||||||
|
cleanup_items_(items.size());
|
||||||
items.clear();
|
items.clear();
|
||||||
batch_start_time = 0;
|
batch_start_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove processed items from the front
|
// Remove processed items from the front with proper cleanup
|
||||||
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
|
void remove_front(size_t count) {
|
||||||
|
cleanup_items_(count);
|
||||||
|
items.erase(items.begin(), items.begin() + count);
|
||||||
|
}
|
||||||
|
|
||||||
bool empty() const { return items.empty(); }
|
bool empty() const { return items.empty(); }
|
||||||
size_t size() const { return items.size(); }
|
size_t size() const { return items.size(); }
|
||||||
@@ -650,30 +682,21 @@ class APIConnection final : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
// 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,
|
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||||
uint8_t estimated_size) {
|
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
|
// Now actually encode and send
|
||||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
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);
|
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
|
// 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) {
|
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);
|
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
|
||||||
|
|||||||
@@ -434,7 +434,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
|
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||||
|
|
||||||
this->reusable_iovs_.clear();
|
this->reusable_iovs_.clear();
|
||||||
this->reusable_iovs_.reserve(packets.size());
|
this->reusable_iovs_.reserve(packets.size());
|
||||||
|
|||||||
@@ -230,7 +230,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
|
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||||
|
|
||||||
this->reusable_iovs_.clear();
|
this->reusable_iovs_.clear();
|
||||||
this->reusable_iovs_.reserve(packets.size());
|
this->reusable_iovs_.reserve(packets.size());
|
||||||
|
|||||||
@@ -70,14 +70,4 @@ extend google.protobuf.FieldOptions {
|
|||||||
// init(size) before adding elements. This eliminates std::vector template overhead
|
// init(size) before adding elements. This eliminates std::vector template overhead
|
||||||
// and is ideal when the exact size is known before populating the array.
|
// and is ideal when the exact size is known before populating the array.
|
||||||
optional bool fixed_vector = 50013 [default=false];
|
optional bool fixed_vector = 50013 [default=false];
|
||||||
|
|
||||||
// container_pointer_no_template: Use a non-template container type for repeated fields
|
|
||||||
// Similar to container_pointer, but for containers that don't take template parameters.
|
|
||||||
// The container type is used as-is without appending element type.
|
|
||||||
// The container must have:
|
|
||||||
// - begin() and end() methods returning iterators
|
|
||||||
// - empty() method
|
|
||||||
// Example: [(container_pointer_no_template) = "light::ColorModeMask"]
|
|
||||||
// generates: const light::ColorModeMask *supported_color_modes{};
|
|
||||||
optional string container_pointer_no_template = 50014;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(10, this->icon_ref_);
|
buffer.encode_string(10, this->icon_ref_);
|
||||||
#endif
|
#endif
|
||||||
buffer.encode_uint32(11, static_cast<uint32_t>(this->entity_category));
|
buffer.encode_uint32(11, static_cast<uint32_t>(this->entity_category));
|
||||||
for (const char *it : *this->supported_preset_modes) {
|
for (const auto &it : *this->supported_preset_modes) {
|
||||||
buffer.encode_string(12, it, strlen(it), true);
|
buffer.encode_string(12, it, true);
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
buffer.encode_uint32(13, this->device_id);
|
buffer.encode_uint32(13, this->device_id);
|
||||||
@@ -376,8 +376,8 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const {
|
|||||||
#endif
|
#endif
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||||
if (!this->supported_preset_modes->empty()) {
|
if (!this->supported_preset_modes->empty()) {
|
||||||
for (const char *it : *this->supported_preset_modes) {
|
for (const auto &it : *this->supported_preset_modes) {
|
||||||
size.add_length_force(1, strlen(it));
|
size.add_length_force(1, it.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
@@ -1179,14 +1179,14 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
for (const auto &it : *this->supported_swing_modes) {
|
for (const auto &it : *this->supported_swing_modes) {
|
||||||
buffer.encode_uint32(14, static_cast<uint32_t>(it), true);
|
buffer.encode_uint32(14, static_cast<uint32_t>(it), true);
|
||||||
}
|
}
|
||||||
for (const char *it : *this->supported_custom_fan_modes) {
|
for (const auto &it : *this->supported_custom_fan_modes) {
|
||||||
buffer.encode_string(15, it, strlen(it), true);
|
buffer.encode_string(15, it, true);
|
||||||
}
|
}
|
||||||
for (const auto &it : *this->supported_presets) {
|
for (const auto &it : *this->supported_presets) {
|
||||||
buffer.encode_uint32(16, static_cast<uint32_t>(it), true);
|
buffer.encode_uint32(16, static_cast<uint32_t>(it), true);
|
||||||
}
|
}
|
||||||
for (const char *it : *this->supported_custom_presets) {
|
for (const auto &it : *this->supported_custom_presets) {
|
||||||
buffer.encode_string(17, it, strlen(it), true);
|
buffer.encode_string(17, it, true);
|
||||||
}
|
}
|
||||||
buffer.encode_bool(18, this->disabled_by_default);
|
buffer.encode_bool(18, this->disabled_by_default);
|
||||||
#ifdef USE_ENTITY_ICON
|
#ifdef USE_ENTITY_ICON
|
||||||
@@ -1229,8 +1229,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->supported_custom_fan_modes->empty()) {
|
if (!this->supported_custom_fan_modes->empty()) {
|
||||||
for (const char *it : *this->supported_custom_fan_modes) {
|
for (const auto &it : *this->supported_custom_fan_modes) {
|
||||||
size.add_length_force(1, strlen(it));
|
size.add_length_force(1, it.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->supported_presets->empty()) {
|
if (!this->supported_presets->empty()) {
|
||||||
@@ -1239,8 +1239,8 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->supported_custom_presets->empty()) {
|
if (!this->supported_custom_presets->empty()) {
|
||||||
for (const char *it : *this->supported_custom_presets) {
|
for (const auto &it : *this->supported_custom_presets) {
|
||||||
size.add_length_force(2, strlen(it));
|
size.add_length_force(2, it.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size.add_bool(2, this->disabled_by_default);
|
size.add_bool(2, this->disabled_by_default);
|
||||||
@@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
#ifdef USE_ENTITY_ICON
|
#ifdef USE_ENTITY_ICON
|
||||||
buffer.encode_string(5, this->icon_ref_);
|
buffer.encode_string(5, this->icon_ref_);
|
||||||
#endif
|
#endif
|
||||||
for (const char *it : *this->options) {
|
for (const auto &it : *this->options) {
|
||||||
buffer.encode_string(6, it, strlen(it), true);
|
buffer.encode_string(6, it, true);
|
||||||
}
|
}
|
||||||
buffer.encode_bool(7, this->disabled_by_default);
|
buffer.encode_bool(7, this->disabled_by_default);
|
||||||
buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category));
|
buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category));
|
||||||
@@ -1492,8 +1492,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_length(1, this->icon_ref_.size());
|
size.add_length(1, this->icon_ref_.size());
|
||||||
#endif
|
#endif
|
||||||
if (!this->options->empty()) {
|
if (!this->options->empty()) {
|
||||||
for (const char *it : *this->options) {
|
for (const auto &it : *this->options) {
|
||||||
size.add_length_force(1, strlen(it));
|
size.add_length_force(1, it.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size.add_bool(1, this->disabled_by_default);
|
size.add_bool(1, this->disabled_by_default);
|
||||||
@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(6, this->disabled_by_default);
|
buffer.encode_bool(6, this->disabled_by_default);
|
||||||
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
||||||
buffer.encode_string(8, this->device_class_ref_);
|
buffer.encode_string(8, this->device_class_ref_);
|
||||||
for (const char *it : *this->event_types) {
|
for (auto &it : this->event_types) {
|
||||||
buffer.encode_string(9, it, strlen(it), true);
|
buffer.encode_string(9, it, true);
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
buffer.encode_uint32(10, this->device_id);
|
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_bool(1, this->disabled_by_default);
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||||
size.add_length(1, this->device_class_ref_.size());
|
size.add_length(1, this->device_class_ref_.size());
|
||||||
if (!this->event_types->empty()) {
|
if (!this->event_types.empty()) {
|
||||||
for (const char *it : *this->event_types) {
|
for (const auto &it : this->event_types) {
|
||||||
size.add_length_force(1, strlen(it));
|
size.add_length_force(1, it.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
|
|||||||
bool supports_speed{false};
|
bool supports_speed{false};
|
||||||
bool supports_direction{false};
|
bool supports_direction{false};
|
||||||
int32_t supported_speed_count{0};
|
int32_t supported_speed_count{0};
|
||||||
const std::vector<const char *> *supported_preset_modes{};
|
const std::set<std::string> *supported_preset_modes{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -790,7 +790,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_light_response"; }
|
const char *message_name() const override { return "list_entities_light_response"; }
|
||||||
#endif
|
#endif
|
||||||
const light::ColorModeMask *supported_color_modes{};
|
const std::set<light::ColorMode> *supported_color_modes{};
|
||||||
float min_mireds{0.0f};
|
float min_mireds{0.0f};
|
||||||
float max_mireds{0.0f};
|
float max_mireds{0.0f};
|
||||||
std::vector<std::string> effects{};
|
std::vector<std::string> effects{};
|
||||||
@@ -1377,16 +1377,16 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
bool supports_current_temperature{false};
|
bool supports_current_temperature{false};
|
||||||
bool supports_two_point_target_temperature{false};
|
bool supports_two_point_target_temperature{false};
|
||||||
const climate::ClimateModeMask *supported_modes{};
|
const std::set<climate::ClimateMode> *supported_modes{};
|
||||||
float visual_min_temperature{0.0f};
|
float visual_min_temperature{0.0f};
|
||||||
float visual_max_temperature{0.0f};
|
float visual_max_temperature{0.0f};
|
||||||
float visual_target_temperature_step{0.0f};
|
float visual_target_temperature_step{0.0f};
|
||||||
bool supports_action{false};
|
bool supports_action{false};
|
||||||
const climate::ClimateFanModeMask *supported_fan_modes{};
|
const std::set<climate::ClimateFanMode> *supported_fan_modes{};
|
||||||
const climate::ClimateSwingModeMask *supported_swing_modes{};
|
const std::set<climate::ClimateSwingMode> *supported_swing_modes{};
|
||||||
const std::vector<const char *> *supported_custom_fan_modes{};
|
const std::set<std::string> *supported_custom_fan_modes{};
|
||||||
const climate::ClimatePresetMask *supported_presets{};
|
const std::set<climate::ClimatePreset> *supported_presets{};
|
||||||
const std::vector<const char *> *supported_custom_presets{};
|
const std::set<std::string> *supported_custom_presets{};
|
||||||
float visual_current_temperature_step{0.0f};
|
float visual_current_temperature_step{0.0f};
|
||||||
bool supports_current_humidity{false};
|
bool supports_current_humidity{false};
|
||||||
bool supports_target_humidity{false};
|
bool supports_target_humidity{false};
|
||||||
@@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_select_response"; }
|
const char *message_name() const override { return "list_entities_select_response"; }
|
||||||
#endif
|
#endif
|
||||||
const FixedVector<const char *> *options{};
|
const std::vector<std::string> *options{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
StringRef device_class_ref_{};
|
StringRef device_class_ref_{};
|
||||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = 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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
|||||||
@@ -88,12 +88,6 @@ static void dump_field(std::string &out, const char *field_name, StringRef value
|
|||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) {
|
|
||||||
append_field_prefix(out, field_name, indent);
|
|
||||||
out.append("'").append(value).append("'");
|
|
||||||
out.append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
|
template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
|
||||||
append_field_prefix(out, field_name, indent);
|
append_field_prefix(out, field_name, indent);
|
||||||
out.append(proto_enum_to_string<T>(value));
|
out.append(proto_enum_to_string<T>(value));
|
||||||
@@ -2053,7 +2047,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
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, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||||
dump_field(out, "device_class", this->device_class_ref_);
|
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);
|
dump_field(out, "event_types", it, 4);
|
||||||
}
|
}
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
@@ -35,7 +34,7 @@ APIServer::APIServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIServer::setup() {
|
void APIServer::setup() {
|
||||||
ControllerRegistry::register_controller(this);
|
this->setup_controller();
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
uint32_t hash = 88491486UL;
|
uint32_t hash = 88491486UL;
|
||||||
@@ -225,7 +224,7 @@ void APIServer::dump_config() {
|
|||||||
" Address: %s:%u\n"
|
" Address: %s:%u\n"
|
||||||
" Listen backlog: %u\n"
|
" Listen backlog: %u\n"
|
||||||
" Max connections: %u",
|
" Max connections: %u",
|
||||||
network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_);
|
network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
||||||
if (!this->noise_ctx_->has_psk()) {
|
if (!this->noise_ctx_->has_psk()) {
|
||||||
@@ -270,7 +269,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len
|
|||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
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) \
|
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
||||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
if (obj->is_internal()) \
|
if (obj->is_internal()) \
|
||||||
@@ -279,6 +278,15 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
|||||||
c->send_##entity_name##_state(obj); \
|
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
|
#ifdef USE_BINARY_SENSOR
|
||||||
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
||||||
#endif
|
#endif
|
||||||
@@ -296,15 +304,15 @@ API_DISPATCH_UPDATE(light::LightState, light)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
API_DISPATCH_UPDATE(sensor::Sensor, sensor)
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
API_DISPATCH_UPDATE(switch_::Switch, switch)
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
@@ -312,7 +320,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
API_DISPATCH_UPDATE(number::Number, number)
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
@@ -328,11 +336,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
API_DISPATCH_UPDATE(text::Text, text)
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#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
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
@@ -348,13 +356,12 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
// Event is a special case - unlike other entities with simple state fields,
|
// Event is a special case - it's the only entity that passes extra parameters to the send method
|
||||||
// events store their state in a member accessed via obj->get_last_event_type()
|
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
||||||
void APIServer::on_event(event::Event *obj) {
|
|
||||||
if (obj->is_internal())
|
if (obj->is_internal())
|
||||||
return;
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_)
|
||||||
c->send_event(obj, obj->get_last_event_type());
|
c->send_event(obj, event_type);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -461,31 +468,6 @@ uint16_t APIServer::get_port() const { return this->port_; }
|
|||||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
|
|
||||||
const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
|
|
||||||
if (!this->noise_pref_.save(&new_psk)) {
|
|
||||||
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ensure it's written immediately
|
|
||||||
if (!global_preferences->sync()) {
|
|
||||||
ESP_LOGW(TAG, "Failed to sync preferences");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
|
|
||||||
if (make_active) {
|
|
||||||
this->set_timeout(100, [this, active_psk]() {
|
|
||||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
|
||||||
this->set_noise_psk(active_psk);
|
|
||||||
for (auto &c : this->clients_) {
|
|
||||||
DisconnectRequest req;
|
|
||||||
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||||
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
// When PSK is set from YAML, this function should never be called
|
// When PSK is set from YAML, this function should never be called
|
||||||
@@ -500,21 +482,27 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SavedNoisePsk new_saved_psk{psk};
|
SavedNoisePsk new_saved_psk{psk};
|
||||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
|
if (!this->noise_pref_.save(&new_saved_psk)) {
|
||||||
make_active);
|
ESP_LOGW(TAG, "Failed to save Noise PSK");
|
||||||
#endif
|
return false;
|
||||||
}
|
}
|
||||||
bool APIServer::clear_noise_psk(bool make_active) {
|
// ensure it's written immediately
|
||||||
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
if (!global_preferences->sync()) {
|
||||||
// When PSK is set from YAML, this function should never be called
|
ESP_LOGW(TAG, "Failed to sync preferences");
|
||||||
// but if it is, reject the change
|
return false;
|
||||||
ESP_LOGW(TAG, "Key set in YAML");
|
}
|
||||||
return false;
|
ESP_LOGD(TAG, "Noise PSK saved");
|
||||||
#else
|
if (make_active) {
|
||||||
SavedNoisePsk empty_psk{};
|
this->set_timeout(100, [this, psk]() {
|
||||||
psk_t empty{};
|
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
|
this->set_noise_psk(psk);
|
||||||
make_active);
|
for (auto &c : this->clients_) {
|
||||||
|
DisconnectRequest req;
|
||||||
|
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool save_noise_psk(psk_t psk, bool make_active = true);
|
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) { noise_ctx_->set_psk(psk); }
|
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
@@ -72,19 +71,19 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_light_update(light::LightState *obj) override;
|
void on_light_update(light::LightState *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void on_sensor_update(sensor::Sensor *obj) override;
|
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
void on_switch_update(switch_::Switch *obj) override;
|
void on_switch_update(switch_::Switch *obj, bool state) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
void on_climate_update(climate::Climate *obj) override;
|
void on_climate_update(climate::Climate *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void on_number_update(number::Number *obj) override;
|
void on_number_update(number::Number *obj, float state) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
void on_date_update(datetime::DateEntity *obj) override;
|
void on_date_update(datetime::DateEntity *obj) override;
|
||||||
@@ -96,10 +95,10 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void on_text_update(text::Text *obj) override;
|
void on_text_update(text::Text *obj, const std::string &state) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#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
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
void on_lock_update(lock::Lock *obj) override;
|
void on_lock_update(lock::Lock *obj) override;
|
||||||
@@ -125,14 +124,8 @@ class APIServer : public Component, public Controller {
|
|||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
|
|
||||||
this->user_services_.assign(services);
|
|
||||||
}
|
|
||||||
#ifdef USE_API_CUSTOM_SERVICES
|
|
||||||
// Only compile push_back method when custom_services: true (external components)
|
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void request_time();
|
void request_time();
|
||||||
#endif
|
#endif
|
||||||
@@ -141,7 +134,7 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void on_event(event::Event *obj) override;
|
void on_event(event::Event *obj, const std::string &event_type) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
void on_update(update::UpdateEntity *obj) override;
|
void on_update(update::UpdateEntity *obj) override;
|
||||||
@@ -181,10 +174,6 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void schedule_reboot_timeout_();
|
void schedule_reboot_timeout_();
|
||||||
#ifdef USE_API_NOISE
|
|
||||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
|
||||||
const psk_t &active_psk, bool make_active);
|
|
||||||
#endif // USE_API_NOISE
|
|
||||||
// Pointers and pointer-like types first (4 bytes each)
|
// Pointers and pointer-like types first (4 bytes each)
|
||||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
@@ -237,7 +226,7 @@ extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-cons
|
|||||||
|
|
||||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
bool check(const Ts &...x) override { return global_api_server->is_connected(); }
|
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||||
public:
|
public:
|
||||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||||
void (T::*callback)(Ts...))
|
void (T::*callback)(Ts...))
|
||||||
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||||
@@ -53,14 +53,8 @@ class CustomAPIDevice {
|
|||||||
template<typename T, typename... Ts>
|
template<typename T, typename... Ts>
|
||||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||||
#ifdef USE_API_CUSTOM_SERVICES
|
|
||||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
#else
|
|
||||||
static_assert(
|
|
||||||
sizeof(T) == 0,
|
|
||||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
template<typename T, typename... Ts>
|
template<typename T, typename... Ts>
|
||||||
@@ -92,14 +86,8 @@ class CustomAPIDevice {
|
|||||||
*/
|
*/
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
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
|
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
#else
|
|
||||||
static_assert(
|
|
||||||
sizeof(T) == 0,
|
|
||||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
|
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
|
||||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantActionRequest resp;
|
HomeassistantActionRequest resp;
|
||||||
std::string service_value = this->service_.value(x...);
|
std::string service_value = this->service_.value(x...);
|
||||||
resp.set_service(StringRef(service_value));
|
resp.set_service(StringRef(service_value));
|
||||||
|
|||||||
@@ -11,58 +11,23 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
|
|||||||
}
|
}
|
||||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||||
|
|
||||||
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
|
|
||||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||||
std::vector<bool> result;
|
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end());
|
||||||
result.reserve(arg.bool_array.size());
|
|
||||||
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
|
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
|
||||||
std::vector<int32_t> result;
|
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end());
|
||||||
result.reserve(arg.int_array.size());
|
|
||||||
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||||
std::vector<float> result;
|
return std::vector<float>(arg.float_array.begin(), arg.float_array.end());
|
||||||
result.reserve(arg.float_array.size());
|
|
||||||
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||||
std::vector<std::string> result;
|
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end());
|
||||||
result.reserve(arg.string_array.size());
|
|
||||||
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New FixedVector const reference versions for YAML-generated services - zero-copy
|
|
||||||
template<>
|
|
||||||
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
|
|
||||||
return arg.bool_array;
|
|
||||||
}
|
|
||||||
template<>
|
|
||||||
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
|
|
||||||
return arg.int_array;
|
|
||||||
}
|
|
||||||
template<>
|
|
||||||
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
|
|
||||||
return arg.float_array;
|
|
||||||
}
|
|
||||||
template<>
|
|
||||||
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
|
|
||||||
const ExecuteServiceArgument &arg) {
|
|
||||||
return arg.string_array;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
|
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||||
|
|
||||||
// Legacy std::vector versions for external components using custom_api_device.h
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
|
||||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
||||||
@@ -74,18 +39,4 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
|
|||||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New FixedVector const reference versions for YAML-generated services
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
|
|
||||||
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
|
|
||||||
}
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
|
|
||||||
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
|
|
||||||
}
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
|
|
||||||
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
|
|
||||||
}
|
|
||||||
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
|
|
||||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -23,57 +23,11 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
|||||||
|
|
||||||
template<typename T> enums::ServiceArgType to_service_arg_type();
|
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 {
|
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||||
public:
|
public:
|
||||||
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
UserServiceBase(std::string name, const std::array<std::string, 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, typename gens<sizeof...(Ts)>::type());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void execute(Ts... x) = 0;
|
|
||||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<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)
|
|
||||||
: name_(std::move(name)), arg_names_(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 {
|
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||||
@@ -93,7 +47,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
|||||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||||
if (req.key != this->key_)
|
if (req.key != this->key_)
|
||||||
return false;
|
return false;
|
||||||
if (req.args.size() != sizeof...(Ts))
|
if (req.args.size() != this->arg_names_.size())
|
||||||
return false;
|
return false;
|
||||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||||
return true;
|
return true;
|
||||||
@@ -105,16 +59,14 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
|||||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heap-allocated strings for runtime-generated names
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
|
||||||
uint32_t key_{0};
|
uint32_t key_{0};
|
||||||
|
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||||
public:
|
public:
|
||||||
// Constructor for static names (YAML-defined services - used by code generator)
|
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||||
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
|
||||||
: UserServiceBase<Ts...>(name, arg_names) {}
|
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace at581x {
|
|||||||
|
|
||||||
template<typename... Ts> class AT581XResetAction : public Action<Ts...>, public Parented<AT581XComponent> {
|
template<typename... Ts> class AT581XResetAction : public Action<Ts...>, public Parented<AT581XComponent> {
|
||||||
public:
|
public:
|
||||||
void play(const Ts &...x) { this->parent_->reset_hardware_frontend(); }
|
void play(Ts... x) { this->parent_->reset_hardware_frontend(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class AT581XSettingsAction : public Action<Ts...>, public Parented<AT581XComponent> {
|
template<typename... Ts> class AT581XSettingsAction : public Action<Ts...>, public Parented<AT581XComponent> {
|
||||||
@@ -25,7 +25,7 @@ template<typename... Ts> class AT581XSettingsAction : public Action<Ts...>, publ
|
|||||||
TEMPLATABLE_VALUE(int, trigger_keep)
|
TEMPLATABLE_VALUE(int, trigger_keep)
|
||||||
TEMPLATABLE_VALUE(int, stage_gain)
|
TEMPLATABLE_VALUE(int, stage_gain)
|
||||||
|
|
||||||
void play(const Ts &...x) {
|
void play(Ts... x) {
|
||||||
if (this->frequency_.has_value()) {
|
if (this->frequency_.has_value()) {
|
||||||
int v = this->frequency_.value(x...);
|
int v = this->frequency_.value(x...);
|
||||||
this->parent_->set_frequency(v);
|
this->parent_->set_frequency(v);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ template<typename... Ts> class SetMicGainAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(float, mic_gain)
|
TEMPLATABLE_VALUE(float, mic_gain)
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); }
|
void play(Ts... x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioAdc *audio_adc_;
|
AudioAdc *audio_adc_;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ template<typename... Ts> class MuteOffAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_off(); }
|
void play(Ts... x) override { this->audio_dac_->set_mute_off(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioDac *audio_dac_;
|
AudioDac *audio_dac_;
|
||||||
@@ -21,7 +21,7 @@ template<typename... Ts> class MuteOnAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->audio_dac_->set_mute_on(); }
|
void play(Ts... x) override { this->audio_dac_->set_mute_on(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioDac *audio_dac_;
|
AudioDac *audio_dac_;
|
||||||
@@ -33,7 +33,7 @@ template<typename... Ts> class SetVolumeAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(float, volume)
|
TEMPLATABLE_VALUE(float, volume)
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioDac *audio_dac_;
|
AudioDac *audio_dac_;
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ namespace bang_bang {
|
|||||||
|
|
||||||
static const char *const TAG = "bang_bang.climate";
|
static const char *const TAG = "bang_bang.climate";
|
||||||
|
|
||||||
BangBangClimate::BangBangClimate()
|
|
||||||
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
|
|
||||||
|
|
||||||
void BangBangClimate::setup() {
|
void BangBangClimate::setup() {
|
||||||
this->sensor_->add_on_state_callback([this](float state) {
|
this->sensor_->add_on_state_callback([this](float state) {
|
||||||
this->current_temperature = state;
|
this->current_temperature = state;
|
||||||
@@ -34,63 +31,53 @@ void BangBangClimate::setup() {
|
|||||||
restore->to_call(this).perform();
|
restore->to_call(this).perform();
|
||||||
} else {
|
} else {
|
||||||
// restore from defaults, change_away handles those for us
|
// restore from defaults, change_away handles those for us
|
||||||
if (this->supports_cool_ && this->supports_heat_) {
|
if (supports_cool_ && supports_heat_) {
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
} else if (this->supports_cool_) {
|
} else if (supports_cool_) {
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
} else if (this->supports_heat_) {
|
} else if (supports_heat_) {
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
}
|
}
|
||||||
this->change_away_(false);
|
this->change_away_(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::control(const climate::ClimateCall &call) {
|
void BangBangClimate::control(const climate::ClimateCall &call) {
|
||||||
if (call.get_mode().has_value()) {
|
if (call.get_mode().has_value())
|
||||||
this->mode = *call.get_mode();
|
this->mode = *call.get_mode();
|
||||||
}
|
if (call.get_target_temperature_low().has_value())
|
||||||
if (call.get_target_temperature_low().has_value()) {
|
|
||||||
this->target_temperature_low = *call.get_target_temperature_low();
|
this->target_temperature_low = *call.get_target_temperature_low();
|
||||||
}
|
if (call.get_target_temperature_high().has_value())
|
||||||
if (call.get_target_temperature_high().has_value()) {
|
|
||||||
this->target_temperature_high = *call.get_target_temperature_high();
|
this->target_temperature_high = *call.get_target_temperature_high();
|
||||||
}
|
if (call.get_preset().has_value())
|
||||||
if (call.get_preset().has_value()) {
|
|
||||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||||
}
|
|
||||||
|
|
||||||
this->compute_state_();
|
this->compute_state_();
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
climate::ClimateTraits BangBangClimate::traits() {
|
climate::ClimateTraits BangBangClimate::traits() {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE |
|
traits.set_supports_current_temperature(true);
|
||||||
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE | climate::CLIMATE_SUPPORTS_ACTION);
|
if (this->humidity_sensor_ != nullptr)
|
||||||
if (this->humidity_sensor_ != nullptr) {
|
traits.set_supports_current_humidity(true);
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY);
|
|
||||||
}
|
|
||||||
traits.set_supported_modes({
|
traits.set_supported_modes({
|
||||||
climate::CLIMATE_MODE_OFF,
|
climate::CLIMATE_MODE_OFF,
|
||||||
});
|
});
|
||||||
if (this->supports_cool_) {
|
if (supports_cool_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||||
}
|
if (supports_heat_)
|
||||||
if (this->supports_heat_) {
|
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||||
}
|
if (supports_cool_ && supports_heat_)
|
||||||
if (this->supports_cool_ && this->supports_heat_) {
|
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||||
}
|
traits.set_supports_two_point_target_temperature(true);
|
||||||
if (this->supports_away_) {
|
if (supports_away_) {
|
||||||
traits.set_supported_presets({
|
traits.set_supported_presets({
|
||||||
climate::CLIMATE_PRESET_HOME,
|
climate::CLIMATE_PRESET_HOME,
|
||||||
climate::CLIMATE_PRESET_AWAY,
|
climate::CLIMATE_PRESET_AWAY,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
traits.set_supports_action(true);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::compute_state_() {
|
void BangBangClimate::compute_state_() {
|
||||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||||
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
||||||
@@ -135,7 +122,6 @@ void BangBangClimate::compute_state_() {
|
|||||||
|
|
||||||
this->switch_to_action_(target_action);
|
this->switch_to_action_(target_action);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
if (action == this->action) {
|
if (action == this->action) {
|
||||||
// already in target mode
|
// already in target mode
|
||||||
@@ -180,7 +166,6 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
|||||||
this->prev_trigger_ = trig;
|
this->prev_trigger_ = trig;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::change_away_(bool away) {
|
void BangBangClimate::change_away_(bool away) {
|
||||||
if (!away) {
|
if (!away) {
|
||||||
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
||||||
@@ -191,26 +176,22 @@ void BangBangClimate::change_away_(bool away) {
|
|||||||
}
|
}
|
||||||
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
|
this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
|
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
|
||||||
this->normal_config_ = normal_config;
|
this->normal_config_ = normal_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &away_config) {
|
void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &away_config) {
|
||||||
this->supports_away_ = true;
|
this->supports_away_ = true;
|
||||||
this->away_config_ = away_config;
|
this->away_config_ = away_config;
|
||||||
}
|
}
|
||||||
|
BangBangClimate::BangBangClimate()
|
||||||
|
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
|
||||||
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||||
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
|
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
|
||||||
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
|
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
|
||||||
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
|
|
||||||
|
|
||||||
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||||
|
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
|
||||||
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||||
|
|
||||||
void BangBangClimate::dump_config() {
|
void BangBangClimate::dump_config() {
|
||||||
LOG_CLIMATE("", "Bang Bang Climate", this);
|
LOG_CLIMATE("", "Bang Bang Climate", this);
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||||||
|
|
||||||
void set_sensor(sensor::Sensor *sensor);
|
void set_sensor(sensor::Sensor *sensor);
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor);
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor);
|
||||||
|
Trigger<> *get_idle_trigger() const;
|
||||||
|
Trigger<> *get_cool_trigger() const;
|
||||||
void set_supports_cool(bool supports_cool);
|
void set_supports_cool(bool supports_cool);
|
||||||
|
Trigger<> *get_heat_trigger() const;
|
||||||
void set_supports_heat(bool supports_heat);
|
void set_supports_heat(bool supports_heat);
|
||||||
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
|
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
|
||||||
void set_away_config(const BangBangClimateTargetTempConfig &away_config);
|
void set_away_config(const BangBangClimateTargetTempConfig &away_config);
|
||||||
|
|
||||||
Trigger<> *get_idle_trigger() const;
|
|
||||||
Trigger<> *get_cool_trigger() const;
|
|
||||||
Trigger<> *get_heat_trigger() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Override control to change settings of the climate device.
|
/// Override control to change settings of the climate device.
|
||||||
void control(const climate::ClimateCall &call) override;
|
void control(const climate::ClimateCall &call) override;
|
||||||
@@ -57,10 +56,16 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||||||
*
|
*
|
||||||
* In idle mode, the controller is assumed to have both heating and cooling disabled.
|
* In idle mode, the controller is assumed to have both heating and cooling disabled.
|
||||||
*/
|
*/
|
||||||
Trigger<> *idle_trigger_{nullptr};
|
Trigger<> *idle_trigger_;
|
||||||
/** The trigger to call when the controller should switch to cooling mode.
|
/** The trigger to call when the controller should switch to cooling mode.
|
||||||
*/
|
*/
|
||||||
Trigger<> *cool_trigger_{nullptr};
|
Trigger<> *cool_trigger_;
|
||||||
|
/** Whether the controller supports cooling.
|
||||||
|
*
|
||||||
|
* A false value for this attribute means that the controller has no cooling action
|
||||||
|
* (for example a thermostat, where only heating and not-heating is possible).
|
||||||
|
*/
|
||||||
|
bool supports_cool_{false};
|
||||||
/** The trigger to call when the controller should switch to heating mode.
|
/** The trigger to call when the controller should switch to heating mode.
|
||||||
*
|
*
|
||||||
* A null value for this attribute means that the controller has no heating action
|
* A null value for this attribute means that the controller has no heating action
|
||||||
@@ -68,23 +73,15 @@ class BangBangClimate : public climate::Climate, public Component {
|
|||||||
* (blinds open) is possible.
|
* (blinds open) is possible.
|
||||||
*/
|
*/
|
||||||
Trigger<> *heat_trigger_{nullptr};
|
Trigger<> *heat_trigger_{nullptr};
|
||||||
|
bool supports_heat_{false};
|
||||||
/** A reference to the trigger that was previously active.
|
/** A reference to the trigger that was previously active.
|
||||||
*
|
*
|
||||||
* This is so that the previous trigger can be stopped before enabling a new one.
|
* This is so that the previous trigger can be stopped before enabling a new one.
|
||||||
*/
|
*/
|
||||||
Trigger<> *prev_trigger_{nullptr};
|
Trigger<> *prev_trigger_{nullptr};
|
||||||
|
|
||||||
/** Whether the controller supports cooling/heating
|
|
||||||
*
|
|
||||||
* A false value for this attribute means that the controller has no respective action
|
|
||||||
* (for example a thermostat, where only heating and not-heating is possible).
|
|
||||||
*/
|
|
||||||
bool supports_cool_{false};
|
|
||||||
bool supports_heat_{false};
|
|
||||||
|
|
||||||
bool supports_away_{false};
|
|
||||||
|
|
||||||
BangBangClimateTargetTempConfig normal_config_{};
|
BangBangClimateTargetTempConfig normal_config_{};
|
||||||
|
bool supports_away_{false};
|
||||||
BangBangClimateTargetTempConfig away_config_{};
|
BangBangClimateTargetTempConfig away_config_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ enum BedjetCommand : uint8_t {
|
|||||||
|
|
||||||
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
|
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
|
||||||
|
|
||||||
static constexpr const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
|
static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
|
||||||
|
static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
|
||||||
|
static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
|
||||||
|
|
||||||
} // namespace bedjet
|
} // namespace bedjet
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace bedjet {
|
|||||||
|
|
||||||
using namespace esphome::climate;
|
using namespace esphome::climate;
|
||||||
|
|
||||||
static const char *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
|
static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
|
||||||
if (fan_step < BEDJET_FAN_SPEED_COUNT)
|
if (fan_step < BEDJET_FAN_SPEED_COUNT)
|
||||||
return BEDJET_FAN_STEP_NAMES[fan_step];
|
return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step];
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t bedjet_fan_speed_to_step(const char *fan_step_percent) {
|
static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) {
|
||||||
for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) {
|
for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) {
|
||||||
if (strcmp(BEDJET_FAN_STEP_NAMES[i], fan_step_percent) == 0) {
|
if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ void BedJetClimate::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode)));
|
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode)));
|
||||||
}
|
}
|
||||||
for (const auto &mode : traits.get_supported_custom_fan_modes()) {
|
for (const auto &mode : traits.get_supported_custom_fan_modes()) {
|
||||||
ESP_LOGCONFIG(TAG, " - %s (c)", mode);
|
ESP_LOGCONFIG(TAG, " - %s (c)", mode.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " Supported presets:");
|
ESP_LOGCONFIG(TAG, " Supported presets:");
|
||||||
@@ -56,7 +56,7 @@ void BedJetClimate::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset)));
|
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset)));
|
||||||
}
|
}
|
||||||
for (const auto &preset : traits.get_supported_custom_presets()) {
|
for (const auto &preset : traits.get_supported_custom_presets()) {
|
||||||
ESP_LOGCONFIG(TAG, " - %s (c)", preset);
|
ESP_LOGCONFIG(TAG, " - %s (c)", preset.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ void BedJetClimate::reset_state_() {
|
|||||||
this->target_temperature = NAN;
|
this->target_temperature = NAN;
|
||||||
this->current_temperature = NAN;
|
this->current_temperature = NAN;
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
if (button_result) {
|
if (button_result) {
|
||||||
this->mode = mode;
|
this->mode = mode;
|
||||||
// We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those
|
// We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,8 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->mode = CLIMATE_MODE_HEAT;
|
this->mode = CLIMATE_MODE_HEAT;
|
||||||
this->set_preset_(CLIMATE_PRESET_BOOST);
|
this->preset = CLIMATE_PRESET_BOOST;
|
||||||
|
this->custom_preset.reset();
|
||||||
}
|
}
|
||||||
} else if (preset == CLIMATE_PRESET_NONE && this->preset.has_value()) {
|
} else if (preset == CLIMATE_PRESET_NONE && this->preset.has_value()) {
|
||||||
if (this->mode == CLIMATE_MODE_HEAT && this->preset == CLIMATE_PRESET_BOOST) {
|
if (this->mode == CLIMATE_MODE_HEAT && this->preset == CLIMATE_PRESET_BOOST) {
|
||||||
@@ -152,7 +153,7 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
result = this->parent_->send_button(heat_button(this->heating_mode_));
|
result = this->parent_->send_button(heat_button(this->heating_mode_));
|
||||||
if (result) {
|
if (result) {
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Ignoring preset '%s' call; with current mode '%s' and preset '%s'",
|
ESP_LOGD(TAG, "Ignoring preset '%s' call; with current mode '%s' and preset '%s'",
|
||||||
@@ -163,27 +164,28 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
ESP_LOGW(TAG, "Unsupported preset: %d", preset);
|
ESP_LOGW(TAG, "Unsupported preset: %d", preset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (call.has_custom_preset()) {
|
} else if (call.get_custom_preset().has_value()) {
|
||||||
const char *preset = call.get_custom_preset();
|
std::string preset = *call.get_custom_preset();
|
||||||
bool result;
|
bool result;
|
||||||
|
|
||||||
if (strcmp(preset, "M1") == 0) {
|
if (preset == "M1") {
|
||||||
result = this->parent_->button_memory1();
|
result = this->parent_->button_memory1();
|
||||||
} else if (strcmp(preset, "M2") == 0) {
|
} else if (preset == "M2") {
|
||||||
result = this->parent_->button_memory2();
|
result = this->parent_->button_memory2();
|
||||||
} else if (strcmp(preset, "M3") == 0) {
|
} else if (preset == "M3") {
|
||||||
result = this->parent_->button_memory3();
|
result = this->parent_->button_memory3();
|
||||||
} else if (strcmp(preset, "LTD HT") == 0) {
|
} else if (preset == "LTD HT") {
|
||||||
result = this->parent_->button_heat();
|
result = this->parent_->button_heat();
|
||||||
} else if (strcmp(preset, "EXT HT") == 0) {
|
} else if (preset == "EXT HT") {
|
||||||
result = this->parent_->button_ext_heat();
|
result = this->parent_->button_ext_heat();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset);
|
ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->set_custom_preset_(preset);
|
this->custom_preset = preset;
|
||||||
|
this->preset.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,16 +207,19 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->set_fan_mode_(fan_mode);
|
this->fan_mode = fan_mode;
|
||||||
|
this->custom_fan_mode.reset();
|
||||||
}
|
}
|
||||||
} else if (call.has_custom_fan_mode()) {
|
} else if (call.get_custom_fan_mode().has_value()) {
|
||||||
const char *fan_mode = call.get_custom_fan_mode();
|
auto fan_mode = *call.get_custom_fan_mode();
|
||||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
|
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
|
||||||
if (fan_index <= 19) {
|
if (fan_index <= 19) {
|
||||||
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index);
|
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(),
|
||||||
|
fan_index);
|
||||||
bool result = this->parent_->set_fan_index(fan_index);
|
bool result = this->parent_->set_fan_index(fan_index);
|
||||||
if (result) {
|
if (result) {
|
||||||
this->set_custom_fan_mode_(fan_mode);
|
this->custom_fan_mode = fan_mode;
|
||||||
|
this->fan_mode.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +245,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
|||||||
|
|
||||||
const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step);
|
const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step);
|
||||||
if (fan_mode_name != nullptr) {
|
if (fan_mode_name != nullptr) {
|
||||||
this->set_custom_fan_mode_(fan_mode_name);
|
this->custom_fan_mode = *fan_mode_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get biorhythm data to determine which preset (M1-3) is running, if any.
|
// TODO: Get biorhythm data to determine which preset (M1-3) is running, if any.
|
||||||
@@ -250,7 +255,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
|||||||
this->mode = CLIMATE_MODE_OFF;
|
this->mode = CLIMATE_MODE_OFF;
|
||||||
this->action = CLIMATE_ACTION_IDLE;
|
this->action = CLIMATE_ACTION_IDLE;
|
||||||
this->fan_mode = CLIMATE_FAN_OFF;
|
this->fan_mode = CLIMATE_FAN_OFF;
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -261,7 +266,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
|||||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||||
this->set_custom_preset_("LTD HT");
|
this->set_custom_preset_("LTD HT");
|
||||||
} else {
|
} else {
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -270,7 +275,7 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
|||||||
this->action = CLIMATE_ACTION_HEATING;
|
this->action = CLIMATE_ACTION_HEATING;
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
} else {
|
} else {
|
||||||
this->set_custom_preset_("EXT HT");
|
this->set_custom_preset_("EXT HT");
|
||||||
}
|
}
|
||||||
@@ -279,19 +284,20 @@ void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
|||||||
case MODE_COOL:
|
case MODE_COOL:
|
||||||
this->mode = CLIMATE_MODE_FAN_ONLY;
|
this->mode = CLIMATE_MODE_FAN_ONLY;
|
||||||
this->action = CLIMATE_ACTION_COOLING;
|
this->action = CLIMATE_ACTION_COOLING;
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_DRY:
|
case MODE_DRY:
|
||||||
this->mode = CLIMATE_MODE_DRY;
|
this->mode = CLIMATE_MODE_DRY;
|
||||||
this->action = CLIMATE_ACTION_DRYING;
|
this->action = CLIMATE_ACTION_DRYING;
|
||||||
this->clear_custom_preset_();
|
this->custom_preset.reset();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_TURBO:
|
case MODE_TURBO:
|
||||||
this->set_preset_(CLIMATE_PRESET_BOOST);
|
this->preset = CLIMATE_PRESET_BOOST;
|
||||||
|
this->custom_preset.reset();
|
||||||
this->mode = CLIMATE_MODE_HEAT;
|
this->mode = CLIMATE_MODE_HEAT;
|
||||||
this->action = CLIMATE_ACTION_HEATING;
|
this->action = CLIMATE_ACTION_HEATING;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
|
|||||||
|
|
||||||
climate::ClimateTraits traits() override {
|
climate::ClimateTraits traits() override {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_ACTION | climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
traits.set_supports_action(true);
|
||||||
|
traits.set_supports_current_temperature(true);
|
||||||
traits.set_supported_modes({
|
traits.set_supported_modes({
|
||||||
climate::CLIMATE_MODE_OFF,
|
climate::CLIMATE_MODE_OFF,
|
||||||
climate::CLIMATE_MODE_HEAT,
|
climate::CLIMATE_MODE_HEAT,
|
||||||
@@ -43,20 +44,28 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
|
|||||||
});
|
});
|
||||||
|
|
||||||
// It would be better if we had a slider for the fan modes.
|
// It would be better if we had a slider for the fan modes.
|
||||||
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
|
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET);
|
||||||
traits.set_supported_presets({
|
traits.set_supported_presets({
|
||||||
// If we support NONE, then have to decide what happens if the user switches to it (turn off?)
|
// If we support NONE, then have to decide what happens if the user switches to it (turn off?)
|
||||||
// climate::CLIMATE_PRESET_NONE,
|
// climate::CLIMATE_PRESET_NONE,
|
||||||
// Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead.
|
// Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead.
|
||||||
climate::CLIMATE_PRESET_BOOST,
|
climate::CLIMATE_PRESET_BOOST,
|
||||||
});
|
});
|
||||||
// String literals are stored in rodata and valid for program lifetime
|
|
||||||
traits.set_supported_custom_presets({
|
traits.set_supported_custom_presets({
|
||||||
this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT",
|
// We could fetch biodata from bedjet and set these names that way.
|
||||||
|
// But then we have to invert the lookup in order to send the right preset.
|
||||||
|
// For now, we can leave them as M1-3 to match the remote buttons.
|
||||||
|
// EXT HT added to match remote button.
|
||||||
|
"EXT HT",
|
||||||
"M1",
|
"M1",
|
||||||
"M2",
|
"M2",
|
||||||
"M3",
|
"M3",
|
||||||
});
|
});
|
||||||
|
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||||
|
traits.add_supported_custom_preset("LTD HT");
|
||||||
|
} else {
|
||||||
|
traits.add_supported_custom_preset("EXT HT");
|
||||||
|
}
|
||||||
traits.set_visual_min_temperature(19.0);
|
traits.set_visual_min_temperature(19.0);
|
||||||
traits.set_visual_max_temperature(43.0);
|
traits.set_visual_max_temperature(43.0);
|
||||||
traits.set_visual_temperature_step(1.0);
|
traits.set_visual_temperature_step(1.0);
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
|
|||||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||||
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
||||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||||
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
|
|
||||||
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
@@ -265,31 +264,20 @@ async def delayed_off_filter_to_code(config, filter_id):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def autorepeat_filter_to_code(config, filter_id):
|
async def autorepeat_filter_to_code(config, filter_id):
|
||||||
|
timings = []
|
||||||
if len(config) > 0:
|
if len(config) > 0:
|
||||||
timings = [
|
timings.extend(
|
||||||
cg.StructInitializer(
|
(conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])
|
||||||
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
|
|
||||||
("delay", conf[CONF_DELAY]),
|
|
||||||
("time_off", conf[CONF_TIME_OFF]),
|
|
||||||
("time_on", conf[CONF_TIME_ON]),
|
|
||||||
)
|
|
||||||
for conf in config
|
for conf in config
|
||||||
]
|
)
|
||||||
else:
|
else:
|
||||||
timings = [
|
timings.append(
|
||||||
cg.StructInitializer(
|
(
|
||||||
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
|
cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds,
|
||||||
("delay", cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds),
|
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
|
||||||
(
|
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
|
||||||
"time_off",
|
|
||||||
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"time_on",
|
|
||||||
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
]
|
)
|
||||||
var = cg.new_Pvariable(filter_id, timings)
|
var = cg.new_Pvariable(filter_id, timings)
|
||||||
await cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
return var
|
return var
|
||||||
@@ -300,7 +288,7 @@ async def lambda_filter_to_code(config, filter_id):
|
|||||||
lambda_ = await cg.process_lambda(
|
lambda_ = await cg.process_lambda(
|
||||||
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||||
)
|
)
|
||||||
return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
|
return cg.new_Pvariable(filter_id, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_filter(
|
@register_filter(
|
||||||
@@ -548,6 +536,11 @@ def binary_sensor_schema(
|
|||||||
return _BINARY_SENSOR_SCHEMA.extend(schema)
|
return _BINARY_SENSOR_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
|
||||||
|
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
|
||||||
|
|
||||||
|
|
||||||
async def setup_binary_sensor_core_(var, config):
|
async def setup_binary_sensor_core_(var, config):
|
||||||
await setup_entity(var, config, "binary_sensor")
|
await setup_entity(var, config, "binary_sensor")
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
|
|||||||
|
|
||||||
class MultiClickTrigger : public Trigger<>, public Component {
|
class MultiClickTrigger : public Trigger<>, public Component {
|
||||||
public:
|
public:
|
||||||
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
|
||||||
: parent_(parent), timing_(timing) {}
|
: parent_(parent), timing_(std::move(timing)) {}
|
||||||
|
|
||||||
void setup() override {
|
void setup() override {
|
||||||
this->last_state_ = this->parent_->get_state_default(false);
|
this->last_state_ = this->parent_->get_state_default(false);
|
||||||
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
|||||||
void trigger_();
|
void trigger_();
|
||||||
|
|
||||||
BinarySensor *parent_;
|
BinarySensor *parent_;
|
||||||
FixedVector<MultiClickTriggerEvent> timing_;
|
std::vector<MultiClickTriggerEvent> timing_;
|
||||||
uint32_t invalid_cooldown_{1000};
|
uint32_t invalid_cooldown_{1000};
|
||||||
optional<size_t> at_index_{};
|
optional<size_t> at_index_{};
|
||||||
bool last_state_{false};
|
bool last_state_{false};
|
||||||
@@ -141,7 +141,7 @@ class StateChangeTrigger : public Trigger<optional<bool>, optional<bool> > {
|
|||||||
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||||
bool check(const Ts &...x) override { return this->parent_->state == this->state_; }
|
bool check(Ts... x) override { return this->parent_->state == this->state_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BinarySensor *parent_;
|
BinarySensor *parent_;
|
||||||
@@ -153,7 +153,7 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...>
|
|||||||
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||||
TEMPLATABLE_VALUE(bool, state)
|
TEMPLATABLE_VALUE(bool, state)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto val = this->state_.value(x...);
|
auto val = this->state_.value(x...);
|
||||||
this->sensor_->publish_state(val);
|
this->sensor_->publish_state(val);
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts..
|
|||||||
public:
|
public:
|
||||||
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->sensor_->invalidate_state(); }
|
void play(Ts... x) override { this->sensor_->invalidate_state(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BinarySensor *sensor_;
|
BinarySensor *sensor_;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#include "binary_sensor.h"
|
#include "binary_sensor.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -39,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
|
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||||
if (this->set_state_(new_state)) {
|
if (this->set_state_(new_state)) {
|
||||||
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +51,7 @@ void BinarySensor::add_filter(Filter *filter) {
|
|||||||
last_filter->next_ = filter;
|
last_filter->next_ = filter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
|
void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
|
||||||
for (Filter *filter : filters) {
|
for (Filter *filter : filters) {
|
||||||
this->add_filter(filter);
|
this->add_filter(filter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/binary_sensor/filter.h"
|
#include "esphome/components/binary_sensor/filter.h"
|
||||||
|
|
||||||
#include <initializer_list>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class BinarySensor : public StatefulEntityBase<bool>, public EntityBase_DeviceCl
|
|||||||
void publish_initial_state(bool new_state);
|
void publish_initial_state(bool new_state);
|
||||||
|
|
||||||
void add_filter(Filter *filter);
|
void add_filter(Filter *filter);
|
||||||
void add_filters(std::initializer_list<Filter *> filters);
|
void add_filters(const std::vector<Filter *> &filters);
|
||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (In most use cases you won't need these)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
|
|
||||||
#include "binary_sensor.h"
|
#include "binary_sensor.h"
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
|
|||||||
|
|
||||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||||
|
|
||||||
AutorepeatFilter::AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) : timings_(timings) {}
|
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||||
|
|
||||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
namespace binary_sensor {
|
namespace binary_sensor {
|
||||||
@@ -80,6 +82,11 @@ class InvertFilter : public Filter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AutorepeatFilterTiming {
|
struct AutorepeatFilterTiming {
|
||||||
|
AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) {
|
||||||
|
this->delay = delay;
|
||||||
|
this->time_off = off;
|
||||||
|
this->time_on = on;
|
||||||
|
}
|
||||||
uint32_t delay;
|
uint32_t delay;
|
||||||
uint32_t time_off;
|
uint32_t time_off;
|
||||||
uint32_t time_on;
|
uint32_t time_on;
|
||||||
@@ -87,7 +94,7 @@ struct AutorepeatFilterTiming {
|
|||||||
|
|
||||||
class AutorepeatFilter : public Filter, public Component {
|
class AutorepeatFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings);
|
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||||
|
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value) override;
|
||||||
|
|
||||||
@@ -97,7 +104,7 @@ class AutorepeatFilter : public Filter, public Component {
|
|||||||
void next_timing_();
|
void next_timing_();
|
||||||
void next_value_(bool val);
|
void next_value_(bool val);
|
||||||
|
|
||||||
FixedVector<AutorepeatFilterTiming> timings_;
|
std::vector<AutorepeatFilterTiming> timings_;
|
||||||
uint8_t active_timing_{0};
|
uint8_t active_timing_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,21 +118,6 @@ class LambdaFilter : public Filter {
|
|||||||
std::function<optional<bool>(bool)> f_;
|
std::function<optional<bool>(bool)> f_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Optimized lambda filter for stateless lambdas (no capture).
|
|
||||||
*
|
|
||||||
* Uses function pointer instead of std::function to reduce memory overhead.
|
|
||||||
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
|
|
||||||
*/
|
|
||||||
class StatelessLambdaFilter : public Filter {
|
|
||||||
public:
|
|
||||||
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
|
|
||||||
|
|
||||||
optional<bool> new_value(bool value) override { return this->f_(value); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
optional<bool> (*f_)(bool);
|
|
||||||
};
|
|
||||||
|
|
||||||
class SettleFilter : public Filter, public Component {
|
class SettleFilter : public Filter, public Component {
|
||||||
public:
|
public:
|
||||||
optional<bool> new_value(bool value) override;
|
optional<bool> new_value(bool value) override;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class BL0906 : public PollingComponent, public uart::UARTDevice {
|
|||||||
|
|
||||||
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
|
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
|
||||||
public:
|
public:
|
||||||
void play(const Ts &...x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bl0906
|
} // namespace bl0906
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
|
|||||||
void CalibrationNumber::setup() {
|
void CalibrationNumber::setup() {
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
if (this->restore_value_) {
|
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)) {
|
if (!this->pref_.load(&value)) {
|
||||||
value = 0.0f;
|
value = 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
)
|
)
|
||||||
from esphome.core import ID
|
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client"]
|
AUTO_LOAD = ["esp32_ble_client"]
|
||||||
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
|
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))
|
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||||
cg.add(var.set_value_template(templ))
|
cg.add(var.set_value_template(templ))
|
||||||
else:
|
else:
|
||||||
# Generate static array in flash to avoid RAM copy
|
cg.add(var.set_value_simple(value))
|
||||||
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)))
|
|
||||||
|
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
cg.add(
|
cg.add(
|
||||||
|
|||||||
@@ -106,30 +106,22 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
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...)) {
|
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||||
this->value_.func = func;
|
this->value_template_ = std::move(func);
|
||||||
this->len_ = -1; // Sentinel value indicates template mode
|
has_simple_value_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store pointer to static data in flash (no RAM copy)
|
void set_value_simple(const std::vector<uint8_t> &value) {
|
||||||
void set_value_simple(const uint8_t *data, size_t len) {
|
this->value_simple_ = value;
|
||||||
this->value_.data = data;
|
has_simple_value_ = true;
|
||||||
this->len_ = len; // Length >= 0 indicates static mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(const Ts &...x) override {}
|
void play(Ts... x) override {}
|
||||||
|
|
||||||
void play_complex(const Ts &...x) override {
|
void play_complex(Ts... x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
this->var_ = std::make_tuple(x...);
|
this->var_ = std::make_tuple(x...);
|
||||||
std::vector<uint8_t> value;
|
auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...);
|
||||||
if (this->len_ >= 0) {
|
|
||||||
// Static mode: copy from flash to vector
|
|
||||||
value.assign(this->value_.data, this->value_.data + this->len_);
|
|
||||||
} else {
|
|
||||||
// Template mode: call function
|
|
||||||
value = this->value_.func(x...);
|
|
||||||
}
|
|
||||||
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
|
||||||
if (!write(value))
|
if (!write(value))
|
||||||
this->play_next_(x...);
|
this->play_next_(x...);
|
||||||
@@ -203,11 +195,9 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
BLEClient *ble_client_;
|
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> value_simple_;
|
||||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||||
const uint8_t *data; // Pointer to static data in flash
|
|
||||||
} value_;
|
|
||||||
espbt::ESPBTUUID service_uuid_;
|
espbt::ESPBTUUID service_uuid_;
|
||||||
espbt::ESPBTUUID char_uuid_;
|
espbt::ESPBTUUID char_uuid_;
|
||||||
std::tuple<Ts...> var_{};
|
std::tuple<Ts...> var_{};
|
||||||
@@ -220,12 +210,12 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
|
|||||||
public:
|
public:
|
||||||
BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
uint32_t passkey;
|
uint32_t passkey;
|
||||||
if (has_simple_value_) {
|
if (has_simple_value_) {
|
||||||
passkey = this->value_.simple;
|
passkey = this->value_simple_;
|
||||||
} else {
|
} else {
|
||||||
passkey = this->value_.template_func(x...);
|
passkey = this->value_template_(x...);
|
||||||
}
|
}
|
||||||
if (passkey > 999999)
|
if (passkey > 999999)
|
||||||
return;
|
return;
|
||||||
@@ -234,63 +224,59 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
|
|||||||
esp_ble_passkey_reply(remote_bda, true, passkey);
|
esp_ble_passkey_reply(remote_bda, true, passkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value_template(uint32_t (*func)(Ts...)) {
|
void set_value_template(std::function<uint32_t(Ts...)> func) {
|
||||||
this->value_.template_func = func;
|
this->value_template_ = std::move(func);
|
||||||
this->has_simple_value_ = false;
|
has_simple_value_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value_simple(const uint32_t &value) {
|
void set_value_simple(const uint32_t &value) {
|
||||||
this->value_.simple = value;
|
this->value_simple_ = value;
|
||||||
this->has_simple_value_ = true;
|
has_simple_value_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BLEClient *parent_{nullptr};
|
BLEClient *parent_{nullptr};
|
||||||
bool has_simple_value_ = true;
|
bool has_simple_value_ = true;
|
||||||
union {
|
uint32_t value_simple_{0};
|
||||||
uint32_t simple;
|
std::function<uint32_t(Ts...)> value_template_{};
|
||||||
uint32_t (*template_func)(Ts...);
|
|
||||||
} value_{.simple = 0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
|
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
esp_bd_addr_t remote_bda;
|
esp_bd_addr_t remote_bda;
|
||||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||||
if (has_simple_value_) {
|
if (has_simple_value_) {
|
||||||
esp_ble_confirm_reply(remote_bda, this->value_.simple);
|
esp_ble_confirm_reply(remote_bda, this->value_simple_);
|
||||||
} else {
|
} else {
|
||||||
esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
|
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value_template(bool (*func)(Ts...)) {
|
void set_value_template(std::function<bool(Ts...)> func) {
|
||||||
this->value_.template_func = func;
|
this->value_template_ = std::move(func);
|
||||||
this->has_simple_value_ = false;
|
has_simple_value_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_value_simple(const bool &value) {
|
void set_value_simple(const bool &value) {
|
||||||
this->value_.simple = value;
|
this->value_simple_ = value;
|
||||||
this->has_simple_value_ = true;
|
has_simple_value_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BLEClient *parent_{nullptr};
|
BLEClient *parent_{nullptr};
|
||||||
bool has_simple_value_ = true;
|
bool has_simple_value_ = true;
|
||||||
union {
|
bool value_simple_{false};
|
||||||
bool simple;
|
std::function<bool(Ts...)> value_template_{};
|
||||||
bool (*template_func)(Ts...);
|
|
||||||
} value_{.simple = false};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
|
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
|
BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
esp_bd_addr_t remote_bda;
|
esp_bd_addr_t remote_bda;
|
||||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||||
esp_ble_remove_bond_device(remote_bda);
|
esp_ble_remove_bond_device(remote_bda);
|
||||||
@@ -325,9 +311,9 @@ template<typename... Ts> class BLEClientConnectAction : public Action<Ts...>, pu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not used since we override play_complex_
|
// not used since we override play_complex_
|
||||||
void play(const Ts &...x) override {}
|
void play(Ts... x) override {}
|
||||||
|
|
||||||
void play_complex(const Ts &...x) override {
|
void play_complex(Ts... x) override {
|
||||||
// it makes no sense to have multiple instances of this running at the same time.
|
// it makes no sense to have multiple instances of this running at the same time.
|
||||||
// this would occur only if the same automation was re-triggered while still
|
// this would occur only if the same automation was re-triggered while still
|
||||||
// running. So just cancel the second chain if this is detected.
|
// running. So just cancel the second chain if this is detected.
|
||||||
@@ -370,9 +356,9 @@ template<typename... Ts> class BLEClientDisconnectAction : public Action<Ts...>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not used since we override play_complex_
|
// not used since we override play_complex_
|
||||||
void play(const Ts &...x) override {}
|
void play(Ts... x) override {}
|
||||||
|
|
||||||
void play_complex(const Ts &...x) override {
|
void play_complex(Ts... x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
if (this->node_state == espbt::ClientState::IDLE) {
|
if (this->node_state == espbt::ClientState::IDLE) {
|
||||||
this->play_next_(x...);
|
this->play_next_(x...);
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||||
// For non-notify characteristics, trigger an immediate read after service discovery
|
|
||||||
// to avoid peripherals disconnecting due to inactivity
|
|
||||||
this->update();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -120,9 +117,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
|||||||
}
|
}
|
||||||
|
|
||||||
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||||
if (this->has_data_to_value_) {
|
if (this->data_to_value_func_.has_value()) {
|
||||||
std::vector<uint8_t> data(value, value + value_len);
|
std::vector<uint8_t> data(value, value + value_len);
|
||||||
return this->data_to_value_func_(data);
|
return (*this->data_to_value_func_)(data);
|
||||||
} else {
|
} else {
|
||||||
return value[0];
|
return value[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace ble_client {
|
|||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
|
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
|
||||||
|
|
||||||
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||||
public:
|
public:
|
||||||
void loop() override;
|
void loop() override;
|
||||||
@@ -31,17 +33,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
|
|||||||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||||
void set_data_to_value(float (*lambda)(const std::vector<uint8_t> &)) {
|
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
|
||||||
this->data_to_value_func_ = lambda;
|
|
||||||
this->has_data_to_value_ = true;
|
|
||||||
}
|
|
||||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||||
uint16_t handle;
|
uint16_t handle;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float parse_data_(uint8_t *value, uint16_t value_len);
|
float parse_data_(uint8_t *value, uint16_t value_len);
|
||||||
bool has_data_to_value_{false};
|
optional<data_to_value_t> data_to_value_func_{};
|
||||||
float (*data_to_value_func_)(const std::vector<uint8_t> &){};
|
|
||||||
bool notify_;
|
bool notify_;
|
||||||
espbt::ESPBTUUID service_uuid_;
|
espbt::ESPBTUUID service_uuid_;
|
||||||
espbt::ESPBTUUID char_uuid_;
|
espbt::ESPBTUUID char_uuid_;
|
||||||
|
|||||||
@@ -79,9 +79,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||||
// For non-notify characteristics, trigger an immediate read after service discovery
|
|
||||||
// to avoid peripherals disconnecting due to inactivity
|
|
||||||
this->update();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE
|
|
||||||
|
|
||||||
AUTO_LOAD = ["zephyr_ble_server"]
|
|
||||||
CODEOWNERS = ["@tomaszduda23"]
|
|
||||||
|
|
||||||
ble_nus_ns = cg.esphome_ns.namespace("ble_nus")
|
|
||||||
BLENUS = ble_nus_ns.class_("BLENUS", cg.Component)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(BLENUS),
|
|
||||||
cv.Optional(CONF_TYPE, default=CONF_LOGS): cv.one_of(
|
|
||||||
*[CONF_LOGS], lower=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
|
||||||
cv.only_with_framework("zephyr"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
|
||||||
zephyr_add_prj_conf("BT_NUS", True)
|
|
||||||
cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS))
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
#ifdef USE_ZEPHYR
|
|
||||||
#include "ble_nus.h"
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <bluetooth/services/nus.h>
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#ifdef USE_LOGGER
|
|
||||||
#include "esphome/components/logger/logger.h"
|
|
||||||
#include "esphome/core/application.h"
|
|
||||||
#endif
|
|
||||||
#include <zephyr/sys/ring_buffer.h>
|
|
||||||
|
|
||||||
namespace esphome::ble_nus {
|
|
||||||
|
|
||||||
constexpr size_t BLE_TX_BUF_SIZE = 2048;
|
|
||||||
|
|
||||||
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
BLENUS *global_ble_nus;
|
|
||||||
RING_BUF_DECLARE(global_ble_tx_ring_buf, BLE_TX_BUF_SIZE);
|
|
||||||
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
|
|
||||||
static const char *const TAG = "ble_nus";
|
|
||||||
|
|
||||||
size_t BLENUS::write_array(const uint8_t *data, size_t len) {
|
|
||||||
if (atomic_get(&this->tx_status_) == TX_DISABLED) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return ring_buf_put(&global_ble_tx_ring_buf, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::connected(bt_conn *conn, uint8_t err) {
|
|
||||||
if (err == 0) {
|
|
||||||
global_ble_nus->conn_.store(bt_conn_ref(conn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::disconnected(bt_conn *conn, uint8_t reason) {
|
|
||||||
if (global_ble_nus->conn_) {
|
|
||||||
bt_conn_unref(global_ble_nus->conn_.load());
|
|
||||||
// Connection array is global static.
|
|
||||||
// Reference can be kept even if disconnected.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::tx_callback(bt_conn *conn) {
|
|
||||||
atomic_cas(&global_ble_nus->tx_status_, TX_BUSY, TX_ENABLED);
|
|
||||||
ESP_LOGVV(TAG, "Sent operation completed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::send_enabled_callback(bt_nus_send_status status) {
|
|
||||||
switch (status) {
|
|
||||||
case BT_NUS_SEND_STATUS_ENABLED:
|
|
||||||
atomic_set(&global_ble_nus->tx_status_, TX_ENABLED);
|
|
||||||
#ifdef USE_LOGGER
|
|
||||||
if (global_ble_nus->expose_log_) {
|
|
||||||
App.schedule_dump_config();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
ESP_LOGD(TAG, "NUS notification has been enabled");
|
|
||||||
break;
|
|
||||||
case BT_NUS_SEND_STATUS_DISABLED:
|
|
||||||
atomic_set(&global_ble_nus->tx_status_, TX_DISABLED);
|
|
||||||
ESP_LOGD(TAG, "NUS notification has been disabled");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::rx_callback(bt_conn *conn, const uint8_t *const data, uint16_t len) {
|
|
||||||
ESP_LOGD(TAG, "Received %d bytes.", len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::setup() {
|
|
||||||
bt_nus_cb callbacks = {
|
|
||||||
.received = rx_callback,
|
|
||||||
.sent = tx_callback,
|
|
||||||
.send_enabled = send_enabled_callback,
|
|
||||||
};
|
|
||||||
|
|
||||||
bt_nus_init(&callbacks);
|
|
||||||
|
|
||||||
static bt_conn_cb conn_callbacks = {
|
|
||||||
.connected = BLENUS::connected,
|
|
||||||
.disconnected = BLENUS::disconnected,
|
|
||||||
};
|
|
||||||
|
|
||||||
bt_conn_cb_register(&conn_callbacks);
|
|
||||||
|
|
||||||
global_ble_nus = this;
|
|
||||||
#ifdef USE_LOGGER
|
|
||||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
|
||||||
logger::global_logger->add_on_log_callback(
|
|
||||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
|
||||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
|
||||||
const char c = '\n';
|
|
||||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG, "ble nus:");
|
|
||||||
ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_));
|
|
||||||
uint32_t mtu = 0;
|
|
||||||
bt_conn *conn = this->conn_.load();
|
|
||||||
if (conn) {
|
|
||||||
mtu = bt_nus_get_mtu(conn);
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " MTU: %u", mtu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLENUS::loop() {
|
|
||||||
if (ring_buf_is_empty(&global_ble_tx_ring_buf)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!atomic_cas(&this->tx_status_, TX_ENABLED, TX_BUSY)) {
|
|
||||||
if (atomic_get(&this->tx_status_) == TX_DISABLED) {
|
|
||||||
ring_buf_reset(&global_ble_tx_ring_buf);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bt_conn *conn = this->conn_.load();
|
|
||||||
if (conn) {
|
|
||||||
conn = bt_conn_ref(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nullptr == conn) {
|
|
||||||
atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t req_len = bt_nus_get_mtu(conn);
|
|
||||||
|
|
||||||
uint8_t *buf;
|
|
||||||
uint32_t size = ring_buf_get_claim(&global_ble_tx_ring_buf, &buf, req_len);
|
|
||||||
|
|
||||||
int err, err2;
|
|
||||||
|
|
||||||
err = bt_nus_send(conn, buf, size);
|
|
||||||
err2 = ring_buf_get_finish(&global_ble_tx_ring_buf, size);
|
|
||||||
if (err2) {
|
|
||||||
// It should no happen.
|
|
||||||
ESP_LOGE(TAG, "Size %u exceeds valid bytes in the ring buffer (%d error)", size, err2);
|
|
||||||
}
|
|
||||||
if (err == 0) {
|
|
||||||
ESP_LOGVV(TAG, "Sent %d bytes", size);
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to send %d bytes (%d error)", size, err);
|
|
||||||
atomic_cas(&this->tx_status_, TX_BUSY, TX_ENABLED);
|
|
||||||
}
|
|
||||||
bt_conn_unref(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::ble_nus
|
|
||||||
#endif
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#ifdef USE_ZEPHYR
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include <shell/shell_bt_nus.h>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace esphome::ble_nus {
|
|
||||||
|
|
||||||
class BLENUS : public Component {
|
|
||||||
enum TxStatus {
|
|
||||||
TX_DISABLED,
|
|
||||||
TX_ENABLED,
|
|
||||||
TX_BUSY,
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
void setup() override;
|
|
||||||
void dump_config() override;
|
|
||||||
void loop() override;
|
|
||||||
size_t write_array(const uint8_t *data, size_t len);
|
|
||||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static void send_enabled_callback(bt_nus_send_status status);
|
|
||||||
static void tx_callback(bt_conn *conn);
|
|
||||||
static void rx_callback(bt_conn *conn, const uint8_t *data, uint16_t len);
|
|
||||||
static void connected(bt_conn *conn, uint8_t err);
|
|
||||||
static void disconnected(bt_conn *conn, uint8_t reason);
|
|
||||||
|
|
||||||
std::atomic<bt_conn *> conn_ = nullptr;
|
|
||||||
bool expose_log_ = false;
|
|
||||||
atomic_t tx_status_ = ATOMIC_INIT(TX_DISABLED);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::ble_nus
|
|
||||||
#endif
|
|
||||||
@@ -155,12 +155,16 @@ esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_par
|
|||||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||||
auto *connection = this->connections_[i];
|
auto *connection = this->connections_[i];
|
||||||
uint64_t conn_addr = connection->get_address();
|
if (connection->get_address() == address)
|
||||||
|
|
||||||
if (conn_addr == address)
|
|
||||||
return connection;
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
if (reserve && conn_addr == 0) {
|
if (!reserve)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||||
|
auto *connection = this->connections_[i];
|
||||||
|
if (connection->get_address() == 0) {
|
||||||
connection->send_service_ = INIT_SENDING_SERVICES;
|
connection->send_service_ = INIT_SENDING_SERVICES;
|
||||||
connection->set_address(address);
|
connection->set_address(address);
|
||||||
// All connections must start at INIT
|
// All connections must start at INIT
|
||||||
@@ -171,6 +175,7 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
||||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature_delta,
|
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||||
IAQ_MODE_OPTIONS, upper=True
|
IAQ_MODE_OPTIONS, upper=True
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ CONFIG_SCHEMA_BASE = (
|
|||||||
cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum(
|
cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum(
|
||||||
VOLTAGE_OPTIONS, upper=True
|
VOLTAGE_OPTIONS, upper=True
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature_delta,
|
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||||
): cv.positive_time_period_minutes,
|
): cv.positive_time_period_minutes,
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ def button_schema(
|
|||||||
return _BUTTON_SCHEMA.extend(schema)
|
return _BUTTON_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
BUTTON_SCHEMA = button_schema(Button)
|
||||||
|
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
|
||||||
|
|
||||||
|
|
||||||
async def setup_button_core_(var, config):
|
async def setup_button_core_(var, config):
|
||||||
await setup_entity(var, config, "button")
|
await setup_entity(var, config, "button")
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ template<typename... Ts> class PressAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit PressAction(Button *button) : button_(button) {}
|
explicit PressAction(Button *button) : button_(button) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->button_->press(); }
|
void play(Ts... x) override { this->button_->press(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Button *button_;
|
Button *button_;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
|
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"]
|
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -176,8 +176,5 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
|||||||
else:
|
else:
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
data = [int(x) for x in data]
|
data = [int(x) for x in data]
|
||||||
# Generate static array in flash to avoid RAM copy
|
cg.add(var.set_data_static(data))
|
||||||
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)))
|
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -112,16 +112,13 @@ class Canbus : public Component {
|
|||||||
|
|
||||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||||
public:
|
public:
|
||||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||||
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
|
this->data_func_ = func;
|
||||||
this->data_.func = func;
|
this->static_ = false;
|
||||||
this->len_ = -1; // Sentinel value indicates template mode
|
|
||||||
}
|
}
|
||||||
|
void set_data_static(const std::vector<uint8_t> &data) {
|
||||||
// Store pointer to static data in flash (no RAM copy)
|
this->data_static_ = data;
|
||||||
void set_data_static(const uint8_t *data, size_t len) {
|
this->static_ = true;
|
||||||
this->data_.data = data;
|
|
||||||
this->len_ = len; // Length >= 0 indicates static mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||||
@@ -132,30 +129,25 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
|
|||||||
this->remote_transmission_request_ = remote_transmission_request;
|
this->remote_transmission_request_ = remote_transmission_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||||
auto use_extended_id =
|
auto use_extended_id =
|
||||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->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->static_) {
|
||||||
if (this->len_ >= 0) {
|
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
|
||||||
// Static mode: copy from flash to vector
|
|
||||||
data.assign(this->data_.data, this->data_.data + this->len_);
|
|
||||||
} else {
|
} else {
|
||||||
// Template mode: call function
|
auto val = this->data_func_(x...);
|
||||||
data = 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:
|
protected:
|
||||||
optional<uint32_t> can_id_{};
|
optional<uint32_t> can_id_{};
|
||||||
optional<bool> use_extended_id_{};
|
optional<bool> use_extended_id_{};
|
||||||
bool remote_transmission_request_{false};
|
bool remote_transmission_request_{false};
|
||||||
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
|
bool static_{false};
|
||||||
union Data {
|
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||||
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
|
std::vector<uint8_t> data_static_{};
|
||||||
const uint8_t *data; // Pointer to static data in flash
|
|
||||||
} data_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||||
|
|||||||
@@ -270,6 +270,11 @@ def climate_schema(
|
|||||||
return _CLIMATE_SCHEMA.extend(schema)
|
return _CLIMATE_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
CLIMATE_SCHEMA = climate_schema(Climate)
|
||||||
|
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
|
||||||
|
|
||||||
|
|
||||||
async def setup_climate_core_(var, config):
|
async def setup_climate_core_(var, config):
|
||||||
await setup_entity(var, config, "climate")
|
await setup_entity(var, config, "climate")
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||||||
TEMPLATABLE_VALUE(std::string, custom_preset)
|
TEMPLATABLE_VALUE(std::string, custom_preset)
|
||||||
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
|
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->climate_->make_call();
|
auto call = this->climate_->make_call();
|
||||||
call.set_mode(this->mode_.optional_value(x...));
|
call.set_mode(this->mode_.optional_value(x...));
|
||||||
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#include "climate.h"
|
#include "climate.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -8,42 +6,6 @@ namespace climate {
|
|||||||
|
|
||||||
static const char *const TAG = "climate";
|
static const char *const TAG = "climate";
|
||||||
|
|
||||||
// Memory-efficient lookup tables
|
|
||||||
struct StringToUint8 {
|
|
||||||
const char *str;
|
|
||||||
const uint8_t value;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr StringToUint8 CLIMATE_MODES_BY_STR[] = {
|
|
||||||
{"OFF", CLIMATE_MODE_OFF},
|
|
||||||
{"AUTO", CLIMATE_MODE_AUTO},
|
|
||||||
{"COOL", CLIMATE_MODE_COOL},
|
|
||||||
{"HEAT", CLIMATE_MODE_HEAT},
|
|
||||||
{"FAN_ONLY", CLIMATE_MODE_FAN_ONLY},
|
|
||||||
{"DRY", CLIMATE_MODE_DRY},
|
|
||||||
{"HEAT_COOL", CLIMATE_MODE_HEAT_COOL},
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr StringToUint8 CLIMATE_FAN_MODES_BY_STR[] = {
|
|
||||||
{"ON", CLIMATE_FAN_ON}, {"OFF", CLIMATE_FAN_OFF}, {"AUTO", CLIMATE_FAN_AUTO},
|
|
||||||
{"LOW", CLIMATE_FAN_LOW}, {"MEDIUM", CLIMATE_FAN_MEDIUM}, {"HIGH", CLIMATE_FAN_HIGH},
|
|
||||||
{"MIDDLE", CLIMATE_FAN_MIDDLE}, {"FOCUS", CLIMATE_FAN_FOCUS}, {"DIFFUSE", CLIMATE_FAN_DIFFUSE},
|
|
||||||
{"QUIET", CLIMATE_FAN_QUIET},
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr StringToUint8 CLIMATE_PRESETS_BY_STR[] = {
|
|
||||||
{"ECO", CLIMATE_PRESET_ECO}, {"AWAY", CLIMATE_PRESET_AWAY}, {"BOOST", CLIMATE_PRESET_BOOST},
|
|
||||||
{"COMFORT", CLIMATE_PRESET_COMFORT}, {"HOME", CLIMATE_PRESET_HOME}, {"SLEEP", CLIMATE_PRESET_SLEEP},
|
|
||||||
{"ACTIVITY", CLIMATE_PRESET_ACTIVITY}, {"NONE", CLIMATE_PRESET_NONE},
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[] = {
|
|
||||||
{"OFF", CLIMATE_SWING_OFF},
|
|
||||||
{"BOTH", CLIMATE_SWING_BOTH},
|
|
||||||
{"VERTICAL", CLIMATE_SWING_VERTICAL},
|
|
||||||
{"HORIZONTAL", CLIMATE_SWING_HORIZONTAL},
|
|
||||||
};
|
|
||||||
|
|
||||||
void ClimateCall::perform() {
|
void ClimateCall::perform() {
|
||||||
this->parent_->control_callback_.call(*this);
|
this->parent_->control_callback_.call(*this);
|
||||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||||
@@ -52,21 +14,21 @@ void ClimateCall::perform() {
|
|||||||
const LogString *mode_s = climate_mode_to_string(*this->mode_);
|
const LogString *mode_s = climate_mode_to_string(*this->mode_);
|
||||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
||||||
}
|
}
|
||||||
if (this->custom_fan_mode_ != nullptr) {
|
if (this->custom_fan_mode_.has_value()) {
|
||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_);
|
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str());
|
||||||
}
|
}
|
||||||
if (this->fan_mode_.has_value()) {
|
if (this->fan_mode_.has_value()) {
|
||||||
this->custom_fan_mode_ = nullptr;
|
this->custom_fan_mode_.reset();
|
||||||
const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
||||||
ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
|
ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
|
||||||
}
|
}
|
||||||
if (this->custom_preset_ != nullptr) {
|
if (this->custom_preset_.has_value()) {
|
||||||
this->preset_.reset();
|
this->preset_.reset();
|
||||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str());
|
||||||
}
|
}
|
||||||
if (this->preset_.has_value()) {
|
if (this->preset_.has_value()) {
|
||||||
this->custom_preset_ = nullptr;
|
this->custom_preset_.reset();
|
||||||
const LogString *preset_s = climate_preset_to_string(*this->preset_);
|
const LogString *preset_s = climate_preset_to_string(*this->preset_);
|
||||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
|
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
|
||||||
}
|
}
|
||||||
@@ -88,44 +50,47 @@ void ClimateCall::perform() {
|
|||||||
}
|
}
|
||||||
this->parent_->control(*this);
|
this->parent_->control(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClimateCall::validate_() {
|
void ClimateCall::validate_() {
|
||||||
auto traits = this->parent_->get_traits();
|
auto traits = this->parent_->get_traits();
|
||||||
if (this->mode_.has_value()) {
|
if (this->mode_.has_value()) {
|
||||||
auto mode = *this->mode_;
|
auto mode = *this->mode_;
|
||||||
if (!traits.supports_mode(mode)) {
|
if (!traits.supports_mode(mode)) {
|
||||||
ESP_LOGW(TAG, " Mode %s not supported", LOG_STR_ARG(climate_mode_to_string(mode)));
|
ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(climate_mode_to_string(mode)));
|
||||||
this->mode_.reset();
|
this->mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->custom_fan_mode_ != nullptr) {
|
if (this->custom_fan_mode_.has_value()) {
|
||||||
if (!traits.supports_custom_fan_mode(this->custom_fan_mode_)) {
|
auto custom_fan_mode = *this->custom_fan_mode_;
|
||||||
ESP_LOGW(TAG, " Fan Mode %s not supported", this->custom_fan_mode_);
|
if (!traits.supports_custom_fan_mode(custom_fan_mode)) {
|
||||||
this->custom_fan_mode_ = nullptr;
|
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", custom_fan_mode.c_str());
|
||||||
|
this->custom_fan_mode_.reset();
|
||||||
}
|
}
|
||||||
} else if (this->fan_mode_.has_value()) {
|
} else if (this->fan_mode_.has_value()) {
|
||||||
auto fan_mode = *this->fan_mode_;
|
auto fan_mode = *this->fan_mode_;
|
||||||
if (!traits.supports_fan_mode(fan_mode)) {
|
if (!traits.supports_fan_mode(fan_mode)) {
|
||||||
ESP_LOGW(TAG, " Fan Mode %s not supported", LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
|
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!",
|
||||||
|
LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
|
||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->custom_preset_ != nullptr) {
|
if (this->custom_preset_.has_value()) {
|
||||||
if (!traits.supports_custom_preset(this->custom_preset_)) {
|
auto custom_preset = *this->custom_preset_;
|
||||||
ESP_LOGW(TAG, " Preset %s not supported", this->custom_preset_);
|
if (!traits.supports_custom_preset(custom_preset)) {
|
||||||
this->custom_preset_ = nullptr;
|
ESP_LOGW(TAG, " Preset %s is not supported by this device!", custom_preset.c_str());
|
||||||
|
this->custom_preset_.reset();
|
||||||
}
|
}
|
||||||
} else if (this->preset_.has_value()) {
|
} else if (this->preset_.has_value()) {
|
||||||
auto preset = *this->preset_;
|
auto preset = *this->preset_;
|
||||||
if (!traits.supports_preset(preset)) {
|
if (!traits.supports_preset(preset)) {
|
||||||
ESP_LOGW(TAG, " Preset %s not supported", LOG_STR_ARG(climate_preset_to_string(preset)));
|
ESP_LOGW(TAG, " Preset %s is not supported by this device!", LOG_STR_ARG(climate_preset_to_string(preset)));
|
||||||
this->preset_.reset();
|
this->preset_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->swing_mode_.has_value()) {
|
if (this->swing_mode_.has_value()) {
|
||||||
auto swing_mode = *this->swing_mode_;
|
auto swing_mode = *this->swing_mode_;
|
||||||
if (!traits.supports_swing_mode(swing_mode)) {
|
if (!traits.supports_swing_mode(swing_mode)) {
|
||||||
ESP_LOGW(TAG, " Swing Mode %s not supported", LOG_STR_ARG(climate_swing_mode_to_string(swing_mode)));
|
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!",
|
||||||
|
LOG_STR_ARG(climate_swing_mode_to_string(swing_mode)));
|
||||||
this->swing_mode_.reset();
|
this->swing_mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,133 +99,159 @@ void ClimateCall::validate_() {
|
|||||||
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
||||||
"with two-point target temperature");
|
"with two-point target temperature!");
|
||||||
this->target_temperature_.reset();
|
this->target_temperature_.reset();
|
||||||
} else if (std::isnan(target)) {
|
} else if (std::isnan(target)) {
|
||||||
ESP_LOGW(TAG, " Target temperature must not be NAN");
|
ESP_LOGW(TAG, " Target temperature must not be NAN!");
|
||||||
this->target_temperature_.reset();
|
this->target_temperature_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
|
if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
|
||||||
if (!traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
if (!traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGW(TAG, " Cannot set low/high target temperature");
|
ESP_LOGW(TAG, " Cannot set low/high target temperature for this device!");
|
||||||
this->target_temperature_low_.reset();
|
this->target_temperature_low_.reset();
|
||||||
this->target_temperature_high_.reset();
|
this->target_temperature_high_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) {
|
if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) {
|
||||||
ESP_LOGW(TAG, " Target temperature low must not be NAN");
|
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||||
this->target_temperature_low_.reset();
|
this->target_temperature_low_.reset();
|
||||||
}
|
}
|
||||||
if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) {
|
if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) {
|
||||||
ESP_LOGW(TAG, " Target temperature high must not be NAN");
|
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||||
this->target_temperature_high_.reset();
|
this->target_temperature_high_.reset();
|
||||||
}
|
}
|
||||||
if (this->target_temperature_low_.has_value() && this->target_temperature_high_.has_value()) {
|
if (this->target_temperature_low_.has_value() && this->target_temperature_high_.has_value()) {
|
||||||
float low = *this->target_temperature_low_;
|
float low = *this->target_temperature_low_;
|
||||||
float high = *this->target_temperature_high_;
|
float high = *this->target_temperature_high_;
|
||||||
if (low > high) {
|
if (low > high) {
|
||||||
ESP_LOGW(TAG, " Target temperature low %.2f must be less than target temperature high %.2f", low, high);
|
ESP_LOGW(TAG, " Target temperature low %.2f must be smaller than target temperature high %.2f!", low, high);
|
||||||
this->target_temperature_low_.reset();
|
this->target_temperature_low_.reset();
|
||||||
this->target_temperature_high_.reset();
|
this->target_temperature_high_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
|
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
|
||||||
this->mode_ = mode;
|
this->mode_ = mode;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
||||||
for (const auto &mode_entry : CLIMATE_MODES_BY_STR) {
|
if (str_equals_case_insensitive(mode, "OFF")) {
|
||||||
if (str_equals_case_insensitive(mode, mode_entry.str)) {
|
this->set_mode(CLIMATE_MODE_OFF);
|
||||||
this->set_mode(static_cast<ClimateMode>(mode_entry.value));
|
} else if (str_equals_case_insensitive(mode, "AUTO")) {
|
||||||
return *this;
|
this->set_mode(CLIMATE_MODE_AUTO);
|
||||||
}
|
} else if (str_equals_case_insensitive(mode, "COOL")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_COOL);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "HEAT")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_HEAT);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "FAN_ONLY")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_FAN_ONLY);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "DRY")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_DRY);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "HEAT_COOL")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_HEAT_COOL);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
||||||
this->fan_mode_ = fan_mode;
|
this->fan_mode_ = fan_mode;
|
||||||
this->custom_fan_mode_ = nullptr;
|
this->custom_fan_mode_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||||
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
|
if (str_equals_case_insensitive(fan_mode, "ON")) {
|
||||||
// Check if it's a standard enum mode first
|
this->set_fan_mode(CLIMATE_FAN_ON);
|
||||||
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
} else if (str_equals_case_insensitive(fan_mode, "OFF")) {
|
||||||
if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) {
|
this->set_fan_mode(CLIMATE_FAN_OFF);
|
||||||
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
} else if (str_equals_case_insensitive(fan_mode, "AUTO")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_AUTO);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "LOW")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_LOW);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "MEDIUM")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_MEDIUM);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "HIGH")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_HIGH);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "MIDDLE")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_MIDDLE);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "FOCUS")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "QUIET")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_QUIET);
|
||||||
|
} else {
|
||||||
|
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||||
|
this->custom_fan_mode_ = fan_mode;
|
||||||
|
this->fan_mode_.reset();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Find the matching pointer from parent climate device
|
|
||||||
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) {
|
|
||||||
this->custom_fan_mode_ = mode_ptr;
|
|
||||||
this->fan_mode_.reset();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); }
|
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
||||||
if (fan_mode.has_value()) {
|
if (fan_mode.has_value()) {
|
||||||
this->set_fan_mode(fan_mode.value());
|
this->set_fan_mode(fan_mode.value());
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
||||||
this->preset_ = preset;
|
this->preset_ = preset;
|
||||||
this->custom_preset_ = nullptr;
|
this->custom_preset_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
||||||
ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
|
if (str_equals_case_insensitive(preset, "ECO")) {
|
||||||
// Check if it's a standard enum preset first
|
this->set_preset(CLIMATE_PRESET_ECO);
|
||||||
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
} else if (str_equals_case_insensitive(preset, "AWAY")) {
|
||||||
if (str_equals_case_insensitive(custom_preset, preset_entry.str)) {
|
this->set_preset(CLIMATE_PRESET_AWAY);
|
||||||
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
} else if (str_equals_case_insensitive(preset, "BOOST")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_BOOST);
|
||||||
|
} else if (str_equals_case_insensitive(preset, "COMFORT")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_COMFORT);
|
||||||
|
} else if (str_equals_case_insensitive(preset, "HOME")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_HOME);
|
||||||
|
} else if (str_equals_case_insensitive(preset, "SLEEP")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_SLEEP);
|
||||||
|
} else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_ACTIVITY);
|
||||||
|
} else if (str_equals_case_insensitive(preset, "NONE")) {
|
||||||
|
this->set_preset(CLIMATE_PRESET_NONE);
|
||||||
|
} else {
|
||||||
|
if (this->parent_->get_traits().supports_custom_preset(preset)) {
|
||||||
|
this->custom_preset_ = preset;
|
||||||
|
this->preset_.reset();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Find the matching pointer from parent climate device
|
|
||||||
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) {
|
|
||||||
this->custom_preset_ = preset_ptr;
|
|
||||||
this->preset_.reset();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); }
|
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
||||||
if (preset.has_value()) {
|
if (preset.has_value()) {
|
||||||
this->set_preset(preset.value());
|
this->set_preset(preset.value());
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) {
|
ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) {
|
||||||
this->swing_mode_ = swing_mode;
|
this->swing_mode_ = swing_mode;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_swing_mode(const std::string &swing_mode) {
|
ClimateCall &ClimateCall::set_swing_mode(const std::string &swing_mode) {
|
||||||
for (const auto &mode_entry : CLIMATE_SWING_MODES_BY_STR) {
|
if (str_equals_case_insensitive(swing_mode, "OFF")) {
|
||||||
if (str_equals_case_insensitive(swing_mode, mode_entry.str)) {
|
this->set_swing_mode(CLIMATE_SWING_OFF);
|
||||||
this->set_swing_mode(static_cast<ClimateSwingMode>(mode_entry.value));
|
} else if (str_equals_case_insensitive(swing_mode, "BOTH")) {
|
||||||
return *this;
|
this->set_swing_mode(CLIMATE_SWING_BOTH);
|
||||||
}
|
} else if (str_equals_case_insensitive(swing_mode, "VERTICAL")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_VERTICAL);
|
||||||
|
} else if (str_equals_case_insensitive(swing_mode, "HORIZONTAL")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %s", this->parent_->get_name().c_str(), swing_mode.c_str());
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %s", this->parent_->get_name().c_str(), swing_mode.c_str());
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,69 +259,59 @@ ClimateCall &ClimateCall::set_target_temperature(float target_temperature) {
|
|||||||
this->target_temperature_ = target_temperature;
|
this->target_temperature_ = target_temperature;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_temperature_low(float target_temperature_low) {
|
ClimateCall &ClimateCall::set_target_temperature_low(float target_temperature_low) {
|
||||||
this->target_temperature_low_ = target_temperature_low;
|
this->target_temperature_low_ = target_temperature_low;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_temperature_high(float target_temperature_high) {
|
ClimateCall &ClimateCall::set_target_temperature_high(float target_temperature_high) {
|
||||||
this->target_temperature_high_ = target_temperature_high;
|
this->target_temperature_high_ = target_temperature_high;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_humidity(float target_humidity) {
|
ClimateCall &ClimateCall::set_target_humidity(float target_humidity) {
|
||||||
this->target_humidity_ = target_humidity;
|
this->target_humidity_ = target_humidity;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; }
|
||||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
||||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||||
const optional<float> &ClimateCall::get_target_humidity() const { return this->target_humidity_; }
|
const optional<float> &ClimateCall::get_target_humidity() const { return this->target_humidity_; }
|
||||||
|
|
||||||
const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; }
|
|
||||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
||||||
|
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
|
||||||
|
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
||||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
|
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
|
||||||
this->target_temperature_high_ = target_temperature_high;
|
this->target_temperature_high_ = target_temperature_high;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_temperature_low(optional<float> target_temperature_low) {
|
ClimateCall &ClimateCall::set_target_temperature_low(optional<float> target_temperature_low) {
|
||||||
this->target_temperature_low_ = target_temperature_low;
|
this->target_temperature_low_ = target_temperature_low;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_temperature(optional<float> target_temperature) {
|
ClimateCall &ClimateCall::set_target_temperature(optional<float> target_temperature) {
|
||||||
this->target_temperature_ = target_temperature;
|
this->target_temperature_ = target_temperature;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_humidity(optional<float> target_humidity) {
|
ClimateCall &ClimateCall::set_target_humidity(optional<float> target_humidity) {
|
||||||
this->target_humidity_ = target_humidity;
|
this->target_humidity_ = target_humidity;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
||||||
this->mode_ = mode;
|
this->mode_ = mode;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) {
|
||||||
this->fan_mode_ = fan_mode;
|
this->fan_mode_ = fan_mode;
|
||||||
this->custom_fan_mode_ = nullptr;
|
this->custom_fan_mode_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(optional<ClimatePreset> preset) {
|
ClimateCall &ClimateCall::set_preset(optional<ClimatePreset> preset) {
|
||||||
this->preset_ = preset;
|
this->preset_ = preset;
|
||||||
this->custom_preset_ = nullptr;
|
this->custom_preset_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) {
|
ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) {
|
||||||
this->swing_mode_ = swing_mode;
|
this->swing_mode_ = swing_mode;
|
||||||
return *this;
|
return *this;
|
||||||
@@ -355,7 +336,6 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
|||||||
return {};
|
return {};
|
||||||
return recovered;
|
return recovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::save_state_() {
|
void Climate::save_state_() {
|
||||||
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
||||||
!defined(CLANG_TIDY)
|
!defined(CLANG_TIDY)
|
||||||
@@ -386,34 +366,30 @@ void Climate::save_state_() {
|
|||||||
state.uses_custom_fan_mode = false;
|
state.uses_custom_fan_mode = false;
|
||||||
state.fan_mode = this->fan_mode.value();
|
state.fan_mode = this->fan_mode.value();
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
|
||||||
state.uses_custom_fan_mode = true;
|
state.uses_custom_fan_mode = true;
|
||||||
const auto &supported = traits.get_supported_custom_fan_modes();
|
const auto &supported = traits.get_supported_custom_fan_modes();
|
||||||
// std::vector maintains insertion order
|
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||||
size_t i = 0;
|
for (size_t i = 0; i < vec.size(); i++) {
|
||||||
for (const char *mode : supported) {
|
if (vec[i] == custom_fan_mode) {
|
||||||
if (strcmp(mode, this->custom_fan_mode_) == 0) {
|
|
||||||
state.custom_fan_mode = i;
|
state.custom_fan_mode = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (traits.get_supports_presets() && preset.has_value()) {
|
if (traits.get_supports_presets() && preset.has_value()) {
|
||||||
state.uses_custom_preset = false;
|
state.uses_custom_preset = false;
|
||||||
state.preset = this->preset.value();
|
state.preset = this->preset.value();
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
|
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
|
||||||
state.uses_custom_preset = true;
|
state.uses_custom_preset = true;
|
||||||
const auto &supported = traits.get_supported_custom_presets();
|
const auto &supported = traits.get_supported_custom_presets();
|
||||||
// std::vector maintains insertion order
|
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||||
size_t i = 0;
|
for (size_t i = 0; i < vec.size(); i++) {
|
||||||
for (const char *preset : supported) {
|
if (vec[i] == custom_preset) {
|
||||||
if (strcmp(preset, this->custom_preset_) == 0) {
|
|
||||||
state.custom_preset = i;
|
state.custom_preset = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (traits.get_supports_swing_modes()) {
|
if (traits.get_supports_swing_modes()) {
|
||||||
@@ -422,7 +398,6 @@ void Climate::save_state_() {
|
|||||||
|
|
||||||
this->rtc_.save(&state);
|
this->rtc_.save(&state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::publish_state() {
|
void Climate::publish_state() {
|
||||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
@@ -434,14 +409,14 @@ void Climate::publish_state() {
|
|||||||
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
||||||
ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
|
ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) {
|
||||||
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_);
|
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str());
|
||||||
}
|
}
|
||||||
if (traits.get_supports_presets() && this->preset.has_value()) {
|
if (traits.get_supports_presets() && this->preset.has_value()) {
|
||||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
|
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
|
if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) {
|
||||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str());
|
||||||
}
|
}
|
||||||
if (traits.get_supports_swing_modes()) {
|
if (traits.get_supports_swing_modes()) {
|
||||||
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
||||||
@@ -465,9 +440,6 @@ void Climate::publish_state() {
|
|||||||
|
|
||||||
// Send state to frontend
|
// Send state to frontend
|
||||||
this->state_callback_.call(*this);
|
this->state_callback_.call(*this);
|
||||||
#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
|
|
||||||
ControllerRegistry::notify_climate_update(this);
|
|
||||||
#endif
|
|
||||||
// Save state
|
// Save state
|
||||||
this->save_state_();
|
this->save_state_();
|
||||||
}
|
}
|
||||||
@@ -497,20 +469,16 @@ ClimateTraits Climate::get_traits() {
|
|||||||
void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) {
|
void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) {
|
||||||
this->visual_min_temperature_override_ = visual_min_temperature_override;
|
this->visual_min_temperature_override_ = visual_min_temperature_override;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
||||||
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::set_visual_temperature_step_override(float target, float current) {
|
void Climate::set_visual_temperature_step_override(float target, float current) {
|
||||||
this->visual_target_temperature_step_override_ = target;
|
this->visual_target_temperature_step_override_ = target;
|
||||||
this->visual_current_temperature_step_override_ = current;
|
this->visual_current_temperature_step_override_ = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::set_visual_min_humidity_override(float visual_min_humidity_override) {
|
void Climate::set_visual_min_humidity_override(float visual_min_humidity_override) {
|
||||||
this->visual_min_humidity_override_ = visual_min_humidity_override;
|
this->visual_min_humidity_override_ = visual_min_humidity_override;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) {
|
void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) {
|
||||||
this->visual_max_humidity_override_ = visual_max_humidity_override;
|
this->visual_max_humidity_override_ = visual_max_humidity_override;
|
||||||
}
|
}
|
||||||
@@ -531,28 +499,17 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
call.set_target_humidity(this->target_humidity);
|
call.set_target_humidity(this->target_humidity);
|
||||||
}
|
}
|
||||||
if (this->uses_custom_fan_mode) {
|
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
|
||||||
call.fan_mode_.reset();
|
|
||||||
call.custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
|
||||||
}
|
|
||||||
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
|
||||||
call.set_fan_mode(this->fan_mode);
|
call.set_fan_mode(this->fan_mode);
|
||||||
}
|
}
|
||||||
if (this->uses_custom_preset) {
|
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
|
||||||
call.preset_.reset();
|
|
||||||
call.custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
|
|
||||||
}
|
|
||||||
} else if (traits.supports_preset(this->preset)) {
|
|
||||||
call.set_preset(this->preset);
|
call.set_preset(this->preset);
|
||||||
}
|
}
|
||||||
if (traits.supports_swing_mode(this->swing_mode)) {
|
if (traits.get_supports_swing_modes()) {
|
||||||
call.set_swing_mode(this->swing_mode);
|
call.set_swing_mode(this->swing_mode);
|
||||||
}
|
}
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||||
auto traits = climate->get_traits();
|
auto traits = climate->get_traits();
|
||||||
climate->mode = this->mode;
|
climate->mode = this->mode;
|
||||||
@@ -566,199 +523,124 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
|||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
climate->target_humidity = this->target_humidity;
|
climate->target_humidity = this->target_humidity;
|
||||||
}
|
}
|
||||||
if (this->uses_custom_fan_mode) {
|
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
|
||||||
climate->fan_mode.reset();
|
|
||||||
climate->custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
|
||||||
}
|
|
||||||
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
|
||||||
climate->fan_mode = this->fan_mode;
|
climate->fan_mode = this->fan_mode;
|
||||||
climate->clear_custom_fan_mode_();
|
|
||||||
}
|
}
|
||||||
if (this->uses_custom_preset) {
|
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
|
||||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||||
climate->preset.reset();
|
const auto &modes = traits.get_supported_custom_fan_modes();
|
||||||
climate->custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
|
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
|
||||||
|
if (custom_fan_mode < modes_vec.size()) {
|
||||||
|
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
|
||||||
}
|
}
|
||||||
} else if (traits.supports_preset(this->preset)) {
|
|
||||||
climate->preset = this->preset;
|
|
||||||
climate->clear_custom_preset_();
|
|
||||||
}
|
}
|
||||||
if (traits.supports_swing_mode(this->swing_mode)) {
|
if (traits.get_supports_presets() && !this->uses_custom_preset) {
|
||||||
|
climate->preset = this->preset;
|
||||||
|
}
|
||||||
|
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
|
||||||
|
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||||
|
const auto &presets = traits.get_supported_custom_presets();
|
||||||
|
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
|
||||||
|
if (custom_preset < presets_vec.size()) {
|
||||||
|
climate->custom_preset = presets_vec[this->custom_preset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (traits.get_supports_swing_modes()) {
|
||||||
climate->swing_mode = this->swing_mode;
|
climate->swing_mode = this->swing_mode;
|
||||||
}
|
}
|
||||||
climate->publish_state();
|
climate->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Template helper for setting primary modes (fan_mode, preset) with mutual exclusion.
|
template<typename T1, typename T2> bool set_alternative(optional<T1> &dst, optional<T2> &alt, const T1 &src) {
|
||||||
*
|
bool is_changed = alt.has_value();
|
||||||
* Climate devices have mutually exclusive mode pairs:
|
alt.reset();
|
||||||
* - fan_mode (enum) vs custom_fan_mode_ (const char*)
|
if (is_changed || dst != src) {
|
||||||
* - preset (enum) vs custom_preset_ (const char*)
|
dst = src;
|
||||||
*
|
is_changed = true;
|
||||||
* Only one mode in each pair can be active at a time. This helper ensures setting a primary
|
|
||||||
* mode automatically clears its corresponding custom mode.
|
|
||||||
*
|
|
||||||
* Example state transitions:
|
|
||||||
* Before: custom_fan_mode_="Turbo", fan_mode=nullopt
|
|
||||||
* Call: set_fan_mode_(CLIMATE_FAN_HIGH)
|
|
||||||
* After: custom_fan_mode_=nullptr, fan_mode=CLIMATE_FAN_HIGH
|
|
||||||
*
|
|
||||||
* @param primary The primary mode optional (fan_mode or preset)
|
|
||||||
* @param custom_ptr Reference to the custom mode pointer (custom_fan_mode_ or custom_preset_)
|
|
||||||
* @param value The new primary mode value to set
|
|
||||||
* @return true if state changed, false if already set to this value
|
|
||||||
*/
|
|
||||||
template<typename T> bool set_primary_mode(optional<T> &primary, const char *&custom_ptr, T value) {
|
|
||||||
// Clear the custom mode (mutual exclusion)
|
|
||||||
bool changed = custom_ptr != nullptr;
|
|
||||||
custom_ptr = nullptr;
|
|
||||||
// Set the primary mode
|
|
||||||
if (changed || !primary.has_value() || primary.value() != value) {
|
|
||||||
primary = value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return is_changed;
|
||||||
}
|
|
||||||
|
|
||||||
/** Template helper for setting custom modes (custom_fan_mode_, custom_preset_) with mutual exclusion.
|
|
||||||
*
|
|
||||||
* This helper ensures setting a custom mode automatically clears its corresponding primary mode.
|
|
||||||
* It also validates that the custom mode exists in the device's supported modes (lifetime safety).
|
|
||||||
*
|
|
||||||
* Example state transitions:
|
|
||||||
* Before: fan_mode=CLIMATE_FAN_HIGH, custom_fan_mode_=nullptr
|
|
||||||
* Call: set_custom_fan_mode_("Turbo")
|
|
||||||
* After: fan_mode=nullopt, custom_fan_mode_="Turbo" (pointer from traits)
|
|
||||||
*
|
|
||||||
* Lifetime Safety:
|
|
||||||
* - found_ptr must come from traits.find_custom_*_mode_()
|
|
||||||
* - Only pointers found in traits are stored, ensuring they remain valid
|
|
||||||
* - Prevents dangling pointers from temporary strings
|
|
||||||
*
|
|
||||||
* @param custom_ptr Reference to the custom mode pointer to set
|
|
||||||
* @param primary The primary mode optional to clear
|
|
||||||
* @param found_ptr The validated pointer from traits (nullptr if not found)
|
|
||||||
* @param has_custom Whether a custom mode is currently active
|
|
||||||
* @return true if state changed, false otherwise
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
bool set_custom_mode(const char *&custom_ptr, optional<T> &primary, const char *found_ptr, bool has_custom) {
|
|
||||||
if (found_ptr != nullptr) {
|
|
||||||
// Clear the primary mode (mutual exclusion)
|
|
||||||
bool changed = primary.has_value();
|
|
||||||
primary.reset();
|
|
||||||
// Set the custom mode (pointer is validated by caller from traits)
|
|
||||||
if (changed || custom_ptr != found_ptr) {
|
|
||||||
custom_ptr = found_ptr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Mode not found in supported modes, clear it if currently set
|
|
||||||
if (has_custom) {
|
|
||||||
custom_ptr = nullptr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
||||||
return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
|
return set_alternative(this->fan_mode, this->custom_fan_mode, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Climate::set_custom_fan_mode_(const char *mode) {
|
bool Climate::set_custom_fan_mode_(const std::string &mode) {
|
||||||
auto traits = this->get_traits();
|
return set_alternative(this->custom_fan_mode, this->fan_mode, mode);
|
||||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode),
|
|
||||||
this->has_custom_fan_mode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); }
|
||||||
|
|
||||||
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
|
bool Climate::set_custom_preset_(const std::string &preset) {
|
||||||
|
return set_alternative(this->custom_preset, this->preset, preset);
|
||||||
bool Climate::set_custom_preset_(const char *preset) {
|
|
||||||
auto traits = this->get_traits();
|
|
||||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset),
|
|
||||||
this->has_custom_preset());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
|
|
||||||
|
|
||||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
|
||||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
|
||||||
return this->get_traits().find_custom_preset_(custom_preset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::dump_traits_(const char *tag) {
|
void Climate::dump_traits_(const char *tag) {
|
||||||
auto traits = this->get_traits();
|
auto traits = this->get_traits();
|
||||||
ESP_LOGCONFIG(tag, "ClimateTraits:");
|
ESP_LOGCONFIG(tag, "ClimateTraits:");
|
||||||
ESP_LOGCONFIG(tag,
|
ESP_LOGCONFIG(tag,
|
||||||
" Visual settings:\n"
|
" [x] Visual settings:\n"
|
||||||
" - Min temperature: %.1f\n"
|
" - Min temperature: %.1f\n"
|
||||||
" - Max temperature: %.1f\n"
|
" - Max temperature: %.1f\n"
|
||||||
" - Temperature step:\n"
|
" - Temperature step:\n"
|
||||||
" Target: %.1f",
|
" Target: %.1f",
|
||||||
traits.get_visual_min_temperature(), traits.get_visual_max_temperature(),
|
traits.get_visual_min_temperature(), traits.get_visual_max_temperature(),
|
||||||
traits.get_visual_target_temperature_step());
|
traits.get_visual_target_temperature_step());
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY |
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY |
|
||||||
climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag,
|
ESP_LOGCONFIG(tag,
|
||||||
" - Min humidity: %.0f\n"
|
" - Min humidity: %.0f\n"
|
||||||
" - Max humidity: %.0f",
|
" - Max humidity: %.0f",
|
||||||
traits.get_visual_min_humidity(), traits.get_visual_max_humidity());
|
traits.get_visual_min_humidity(), traits.get_visual_max_humidity());
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " Supports two-point target temperature");
|
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||||
ESP_LOGCONFIG(tag, " Supports current temperature");
|
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag, " Supports target humidity");
|
ESP_LOGCONFIG(tag, " [x] Supports target humidity");
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||||
ESP_LOGCONFIG(tag, " Supports current humidity");
|
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||||
ESP_LOGCONFIG(tag, " Supports action");
|
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_modes().empty()) {
|
if (!traits.get_supported_modes().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported modes:");
|
ESP_LOGCONFIG(tag, " [x] Supported modes:");
|
||||||
for (ClimateMode m : traits.get_supported_modes())
|
for (ClimateMode m : traits.get_supported_modes())
|
||||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m)));
|
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m)));
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_fan_modes().empty()) {
|
if (!traits.get_supported_fan_modes().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported fan modes:");
|
ESP_LOGCONFIG(tag, " [x] Supported fan modes:");
|
||||||
for (ClimateFanMode m : traits.get_supported_fan_modes())
|
for (ClimateFanMode m : traits.get_supported_fan_modes())
|
||||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m)));
|
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m)));
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported custom fan modes:");
|
ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:");
|
||||||
for (const char *s : traits.get_supported_custom_fan_modes())
|
for (const std::string &s : traits.get_supported_custom_fan_modes())
|
||||||
ESP_LOGCONFIG(tag, " - %s", s);
|
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_presets().empty()) {
|
if (!traits.get_supported_presets().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported presets:");
|
ESP_LOGCONFIG(tag, " [x] Supported presets:");
|
||||||
for (ClimatePreset p : traits.get_supported_presets())
|
for (ClimatePreset p : traits.get_supported_presets())
|
||||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p)));
|
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p)));
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty()) {
|
if (!traits.get_supported_custom_presets().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported custom presets:");
|
ESP_LOGCONFIG(tag, " [x] Supported custom presets:");
|
||||||
for (const char *s : traits.get_supported_custom_presets())
|
for (const std::string &s : traits.get_supported_custom_presets())
|
||||||
ESP_LOGCONFIG(tag, " - %s", s);
|
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_swing_modes().empty()) {
|
if (!traits.get_supported_swing_modes().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported swing modes:");
|
ESP_LOGCONFIG(tag, " [x] Supported swing modes:");
|
||||||
for (ClimateSwingMode m : traits.get_supported_swing_modes())
|
for (ClimateSwingMode m : traits.get_supported_swing_modes())
|
||||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m)));
|
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class Climate;
|
|||||||
class ClimateCall {
|
class ClimateCall {
|
||||||
public:
|
public:
|
||||||
explicit ClimateCall(Climate *parent) : parent_(parent) {}
|
explicit ClimateCall(Climate *parent) : parent_(parent) {}
|
||||||
friend struct ClimateDeviceRestoreState;
|
|
||||||
|
|
||||||
/// Set the mode of the climate device.
|
/// Set the mode of the climate device.
|
||||||
ClimateCall &set_mode(ClimateMode mode);
|
ClimateCall &set_mode(ClimateMode mode);
|
||||||
@@ -77,8 +76,6 @@ class ClimateCall {
|
|||||||
ClimateCall &set_fan_mode(const std::string &fan_mode);
|
ClimateCall &set_fan_mode(const std::string &fan_mode);
|
||||||
/// Set the fan mode of the climate device based on a string.
|
/// Set the fan mode of the climate device based on a string.
|
||||||
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
||||||
/// Set the custom fan mode of the climate device.
|
|
||||||
ClimateCall &set_fan_mode(const char *custom_fan_mode);
|
|
||||||
/// Set the swing mode of the climate device.
|
/// Set the swing mode of the climate device.
|
||||||
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
||||||
/// Set the swing mode of the climate device.
|
/// Set the swing mode of the climate device.
|
||||||
@@ -93,41 +90,34 @@ class ClimateCall {
|
|||||||
ClimateCall &set_preset(const std::string &preset);
|
ClimateCall &set_preset(const std::string &preset);
|
||||||
/// Set the preset of the climate device based on a string.
|
/// Set the preset of the climate device based on a string.
|
||||||
ClimateCall &set_preset(optional<std::string> preset);
|
ClimateCall &set_preset(optional<std::string> preset);
|
||||||
/// Set the custom preset of the climate device.
|
|
||||||
ClimateCall &set_preset(const char *custom_preset);
|
|
||||||
|
|
||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
|
const optional<ClimateMode> &get_mode() const;
|
||||||
const optional<float> &get_target_temperature() const;
|
const optional<float> &get_target_temperature() const;
|
||||||
const optional<float> &get_target_temperature_low() const;
|
const optional<float> &get_target_temperature_low() const;
|
||||||
const optional<float> &get_target_temperature_high() const;
|
const optional<float> &get_target_temperature_high() const;
|
||||||
const optional<float> &get_target_humidity() const;
|
const optional<float> &get_target_humidity() const;
|
||||||
|
|
||||||
const optional<ClimateMode> &get_mode() const;
|
|
||||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||||
|
const optional<std::string> &get_custom_fan_mode() const;
|
||||||
const optional<ClimatePreset> &get_preset() const;
|
const optional<ClimatePreset> &get_preset() const;
|
||||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
const optional<std::string> &get_custom_preset() const;
|
||||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
|
||||||
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
|
|
||||||
bool has_custom_preset() const { return this->custom_preset_ != nullptr; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void validate_();
|
void validate_();
|
||||||
|
|
||||||
Climate *const parent_;
|
Climate *const parent_;
|
||||||
|
optional<ClimateMode> mode_;
|
||||||
optional<float> target_temperature_;
|
optional<float> target_temperature_;
|
||||||
optional<float> target_temperature_low_;
|
optional<float> target_temperature_low_;
|
||||||
optional<float> target_temperature_high_;
|
optional<float> target_temperature_high_;
|
||||||
optional<float> target_humidity_;
|
optional<float> target_humidity_;
|
||||||
optional<ClimateMode> mode_;
|
|
||||||
optional<ClimateFanMode> fan_mode_;
|
optional<ClimateFanMode> fan_mode_;
|
||||||
optional<ClimateSwingMode> swing_mode_;
|
optional<ClimateSwingMode> swing_mode_;
|
||||||
|
optional<std::string> custom_fan_mode_;
|
||||||
optional<ClimatePreset> preset_;
|
optional<ClimatePreset> preset_;
|
||||||
|
optional<std::string> custom_preset_;
|
||||||
private:
|
|
||||||
const char *custom_fan_mode_{nullptr};
|
|
||||||
const char *custom_preset_{nullptr};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Struct used to save the state of the climate device in restore memory.
|
/// Struct used to save the state of the climate device in restore memory.
|
||||||
@@ -179,6 +169,47 @@ class Climate : public EntityBase {
|
|||||||
public:
|
public:
|
||||||
Climate() {}
|
Climate() {}
|
||||||
|
|
||||||
|
/// The active mode of the climate device.
|
||||||
|
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||||
|
|
||||||
|
/// The active state of the climate device.
|
||||||
|
ClimateAction action{CLIMATE_ACTION_OFF};
|
||||||
|
|
||||||
|
/// The current temperature of the climate device, as reported from the integration.
|
||||||
|
float current_temperature{NAN};
|
||||||
|
|
||||||
|
/// The current humidity of the climate device, as reported from the integration.
|
||||||
|
float current_humidity{NAN};
|
||||||
|
|
||||||
|
union {
|
||||||
|
/// The target temperature of the climate device.
|
||||||
|
float target_temperature;
|
||||||
|
struct {
|
||||||
|
/// The minimum target temperature of the climate device, for climate devices with split target temperature.
|
||||||
|
float target_temperature_low{NAN};
|
||||||
|
/// The maximum target temperature of the climate device, for climate devices with split target temperature.
|
||||||
|
float target_temperature_high{NAN};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The target humidity of the climate device.
|
||||||
|
float target_humidity;
|
||||||
|
|
||||||
|
/// The active fan mode of the climate device.
|
||||||
|
optional<ClimateFanMode> fan_mode;
|
||||||
|
|
||||||
|
/// The active swing mode of the climate device.
|
||||||
|
ClimateSwingMode swing_mode;
|
||||||
|
|
||||||
|
/// The active custom fan mode of the climate device.
|
||||||
|
optional<std::string> custom_fan_mode;
|
||||||
|
|
||||||
|
/// The active preset of the climate device.
|
||||||
|
optional<ClimatePreset> preset;
|
||||||
|
|
||||||
|
/// The active custom preset mode of the climate device.
|
||||||
|
optional<std::string> custom_preset;
|
||||||
|
|
||||||
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
||||||
* (using publish_state), this callback will be called.
|
* (using publish_state), this callback will be called.
|
||||||
*
|
*
|
||||||
@@ -220,78 +251,20 @@ class Climate : public EntityBase {
|
|||||||
void set_visual_min_humidity_override(float visual_min_humidity_override);
|
void set_visual_min_humidity_override(float visual_min_humidity_override);
|
||||||
void set_visual_max_humidity_override(float visual_max_humidity_override);
|
void set_visual_max_humidity_override(float visual_max_humidity_override);
|
||||||
|
|
||||||
/// Check if a custom fan mode is currently active.
|
|
||||||
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
|
|
||||||
|
|
||||||
/// Check if a custom preset is currently active.
|
|
||||||
bool has_custom_preset() const { return this->custom_preset_ != nullptr; }
|
|
||||||
|
|
||||||
/// The current temperature of the climate device, as reported from the integration.
|
|
||||||
float current_temperature{NAN};
|
|
||||||
|
|
||||||
/// The current humidity of the climate device, as reported from the integration.
|
|
||||||
float current_humidity{NAN};
|
|
||||||
|
|
||||||
union {
|
|
||||||
/// The target temperature of the climate device.
|
|
||||||
float target_temperature;
|
|
||||||
struct {
|
|
||||||
/// The minimum target temperature of the climate device, for climate devices with split target temperature.
|
|
||||||
float target_temperature_low{NAN};
|
|
||||||
/// The maximum target temperature of the climate device, for climate devices with split target temperature.
|
|
||||||
float target_temperature_high{NAN};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The target humidity of the climate device.
|
|
||||||
float target_humidity;
|
|
||||||
|
|
||||||
/// The active fan mode of the climate device.
|
|
||||||
optional<ClimateFanMode> fan_mode;
|
|
||||||
|
|
||||||
/// The active preset of the climate device.
|
|
||||||
optional<ClimatePreset> preset;
|
|
||||||
|
|
||||||
/// The active mode of the climate device.
|
|
||||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
|
||||||
|
|
||||||
/// The active state of the climate device.
|
|
||||||
ClimateAction action{CLIMATE_ACTION_OFF};
|
|
||||||
|
|
||||||
/// The active swing mode of the climate device.
|
|
||||||
ClimateSwingMode swing_mode{CLIMATE_SWING_OFF};
|
|
||||||
|
|
||||||
/// Get the active custom fan mode (read-only access).
|
|
||||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
|
||||||
|
|
||||||
/// Get the active custom preset (read-only access).
|
|
||||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend ClimateCall;
|
friend ClimateCall;
|
||||||
friend struct ClimateDeviceRestoreState;
|
|
||||||
|
|
||||||
/// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed.
|
/// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed.
|
||||||
bool set_fan_mode_(ClimateFanMode mode);
|
bool set_fan_mode_(ClimateFanMode mode);
|
||||||
|
|
||||||
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
|
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
|
||||||
bool set_custom_fan_mode_(const char *mode);
|
bool set_custom_fan_mode_(const std::string &mode);
|
||||||
/// Clear custom fan mode.
|
|
||||||
void clear_custom_fan_mode_();
|
|
||||||
|
|
||||||
/// Set preset. Reset custom preset. Return true if preset has been changed.
|
/// Set preset. Reset custom preset. Return true if preset has been changed.
|
||||||
bool set_preset_(ClimatePreset preset);
|
bool set_preset_(ClimatePreset preset);
|
||||||
|
|
||||||
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
|
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
|
||||||
bool set_custom_preset_(const char *preset);
|
bool set_custom_preset_(const std::string &preset);
|
||||||
/// Clear custom preset.
|
|
||||||
void clear_custom_preset_();
|
|
||||||
|
|
||||||
/// Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
|
|
||||||
const char *find_custom_fan_mode_(const char *custom_fan_mode);
|
|
||||||
|
|
||||||
/// Find and return the matching custom preset pointer from traits, or nullptr if not found.
|
|
||||||
const char *find_custom_preset_(const char *custom_preset);
|
|
||||||
|
|
||||||
/** Get the default traits of this climate device.
|
/** Get the default traits of this climate device.
|
||||||
*
|
*
|
||||||
@@ -328,21 +301,6 @@ class Climate : public EntityBase {
|
|||||||
optional<float> visual_current_temperature_step_override_{};
|
optional<float> visual_current_temperature_step_override_{};
|
||||||
optional<float> visual_min_humidity_override_{};
|
optional<float> visual_min_humidity_override_{};
|
||||||
optional<float> visual_max_humidity_override_{};
|
optional<float> visual_max_humidity_override_{};
|
||||||
|
|
||||||
private:
|
|
||||||
/** The active custom fan mode (private - enforces use of safe setters).
|
|
||||||
*
|
|
||||||
* Points to an entry in traits.supported_custom_fan_modes_ or nullptr.
|
|
||||||
* Use get_custom_fan_mode() to read, set_custom_fan_mode_() to modify.
|
|
||||||
*/
|
|
||||||
const char *custom_fan_mode_{nullptr};
|
|
||||||
|
|
||||||
/** The active custom preset (private - enforces use of safe setters).
|
|
||||||
*
|
|
||||||
* Points to an entry in traits.supported_custom_presets_ or nullptr.
|
|
||||||
* Use get_custom_preset() to read, set_custom_preset_() to modify.
|
|
||||||
*/
|
|
||||||
const char *custom_preset_{nullptr};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace esphome {
|
|||||||
namespace climate {
|
namespace climate {
|
||||||
|
|
||||||
/// Enum for all modes a climate device can be in.
|
/// Enum for all modes a climate device can be in.
|
||||||
/// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value
|
|
||||||
enum ClimateMode : uint8_t {
|
enum ClimateMode : uint8_t {
|
||||||
/// The climate device is off
|
/// The climate device is off
|
||||||
CLIMATE_MODE_OFF = 0,
|
CLIMATE_MODE_OFF = 0,
|
||||||
@@ -25,7 +24,7 @@ enum ClimateMode : uint8_t {
|
|||||||
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
|
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
|
||||||
* The target temperature can't be adjusted when in this mode.
|
* The target temperature can't be adjusted when in this mode.
|
||||||
*/
|
*/
|
||||||
CLIMATE_MODE_AUTO = 6 // Update ClimateModeMask in climate_traits.h if adding values after this
|
CLIMATE_MODE_AUTO = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
||||||
@@ -44,7 +43,6 @@ enum ClimateAction : uint8_t {
|
|||||||
CLIMATE_ACTION_FAN = 6,
|
CLIMATE_ACTION_FAN = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value
|
|
||||||
enum ClimateFanMode : uint8_t {
|
enum ClimateFanMode : uint8_t {
|
||||||
/// The fan mode is set to On
|
/// The fan mode is set to On
|
||||||
CLIMATE_FAN_ON = 0,
|
CLIMATE_FAN_ON = 0,
|
||||||
@@ -65,11 +63,10 @@ enum ClimateFanMode : uint8_t {
|
|||||||
/// The fan mode is set to Diffuse
|
/// The fan mode is set to Diffuse
|
||||||
CLIMATE_FAN_DIFFUSE = 8,
|
CLIMATE_FAN_DIFFUSE = 8,
|
||||||
/// The fan mode is set to Quiet
|
/// The fan mode is set to Quiet
|
||||||
CLIMATE_FAN_QUIET = 9, // Update ClimateFanModeMask in climate_traits.h if adding values after this
|
CLIMATE_FAN_QUIET = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum for all modes a climate swing can be in
|
/// Enum for all modes a climate swing can be in
|
||||||
/// NOTE: If adding values, update ClimateSwingModeMask in climate_traits.h to use the new last value
|
|
||||||
enum ClimateSwingMode : uint8_t {
|
enum ClimateSwingMode : uint8_t {
|
||||||
/// The swing mode is set to Off
|
/// The swing mode is set to Off
|
||||||
CLIMATE_SWING_OFF = 0,
|
CLIMATE_SWING_OFF = 0,
|
||||||
@@ -78,11 +75,10 @@ enum ClimateSwingMode : uint8_t {
|
|||||||
/// The fan mode is set to Vertical
|
/// The fan mode is set to Vertical
|
||||||
CLIMATE_SWING_VERTICAL = 2,
|
CLIMATE_SWING_VERTICAL = 2,
|
||||||
/// The fan mode is set to Horizontal
|
/// The fan mode is set to Horizontal
|
||||||
CLIMATE_SWING_HORIZONTAL = 3, // Update ClimateSwingModeMask in climate_traits.h if adding values after this
|
CLIMATE_SWING_HORIZONTAL = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum for all preset modes
|
/// Enum for all preset modes
|
||||||
/// NOTE: If adding values, update ClimatePresetMask in climate_traits.h to use the new last value
|
|
||||||
enum ClimatePreset : uint8_t {
|
enum ClimatePreset : uint8_t {
|
||||||
/// No preset is active
|
/// No preset is active
|
||||||
CLIMATE_PRESET_NONE = 0,
|
CLIMATE_PRESET_NONE = 0,
|
||||||
@@ -99,7 +95,7 @@ enum ClimatePreset : uint8_t {
|
|||||||
/// Device is prepared for sleep
|
/// Device is prepared for sleep
|
||||||
CLIMATE_PRESET_SLEEP = 6,
|
CLIMATE_PRESET_SLEEP = 6,
|
||||||
/// Device is reacting to activity (e.g., movement sensors)
|
/// Device is reacting to activity (e.g., movement sensors)
|
||||||
CLIMATE_PRESET_ACTIVITY = 7, // Update ClimatePresetMask in climate_traits.h if adding values after this
|
CLIMATE_PRESET_ACTIVITY = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ClimateFeature : uint32_t {
|
enum ClimateFeature : uint32_t {
|
||||||
|
|||||||
@@ -1,43 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
#include "climate_mode.h"
|
|
||||||
#include "esphome/core/finite_set_mask.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "climate_mode.h"
|
||||||
|
#include <set>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
|
#ifdef USE_API
|
||||||
|
namespace api {
|
||||||
|
class APIConnection;
|
||||||
|
} // namespace api
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace climate {
|
namespace climate {
|
||||||
|
|
||||||
// Type aliases for climate enum bitmasks
|
|
||||||
// These replace std::set<EnumType> to eliminate red-black tree overhead
|
|
||||||
// For contiguous enums starting at 0, DefaultBitPolicy provides 1:1 mapping (enum value = bit position)
|
|
||||||
// Bitmask size is automatically calculated from the last enum value
|
|
||||||
using ClimateModeMask = FiniteSetMask<ClimateMode, DefaultBitPolicy<ClimateMode, CLIMATE_MODE_AUTO + 1>>;
|
|
||||||
using ClimateFanModeMask = FiniteSetMask<ClimateFanMode, DefaultBitPolicy<ClimateFanMode, CLIMATE_FAN_QUIET + 1>>;
|
|
||||||
using ClimateSwingModeMask =
|
|
||||||
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
|
|
||||||
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
|
|
||||||
|
|
||||||
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
|
||||||
// Avoids std::find template overhead
|
|
||||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
|
||||||
for (const char *item : vec) {
|
|
||||||
if (strcmp(item, value) == 0)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find and return matching pointer from vector, or nullptr if not found
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This class contains all static data for climate devices.
|
/** This class contains all static data for climate devices.
|
||||||
*
|
*
|
||||||
* All climate devices must support these features:
|
* All climate devices must support these features:
|
||||||
@@ -65,11 +41,7 @@ inline const char *vector_find(const std::vector<const char *> &vec, const char
|
|||||||
* - temperature step - the step with which to increase/decrease target temperature.
|
* - temperature step - the step with which to increase/decrease target temperature.
|
||||||
* This also affects with how many decimal places the temperature is shown
|
* This also affects with how many decimal places the temperature is shown
|
||||||
*/
|
*/
|
||||||
class Climate; // Forward declaration
|
|
||||||
|
|
||||||
class ClimateTraits {
|
class ClimateTraits {
|
||||||
friend class Climate; // Allow Climate to access protected find methods
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Get/set feature flags (see ClimateFeatures enum in climate_mode.h)
|
/// Get/set feature flags (see ClimateFeatures enum in climate_mode.h)
|
||||||
uint32_t get_feature_flags() const { return this->feature_flags_; }
|
uint32_t get_feature_flags() const { return this->feature_flags_; }
|
||||||
@@ -135,74 +107,90 @@ class ClimateTraits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
|
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
|
||||||
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_only_mode(bool supports_fan_only_mode) {
|
||||||
|
set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode);
|
||||||
|
}
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||||
|
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
|
||||||
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
||||||
const ClimateModeMask &get_supported_modes() const { return this->supported_modes_; }
|
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
|
||||||
|
|
||||||
void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; }
|
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
|
||||||
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
||||||
|
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||||
|
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
|
||||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
|
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
|
||||||
bool get_supports_fan_modes() const {
|
bool get_supports_fan_modes() const {
|
||||||
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
||||||
}
|
}
|
||||||
const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||||
|
|
||||||
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
|
||||||
this->supported_custom_fan_modes_ = modes;
|
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||||
}
|
|
||||||
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
|
||||||
this->supported_custom_fan_modes_ = modes;
|
|
||||||
}
|
|
||||||
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
|
||||||
this->supported_custom_fan_modes_.assign(modes, modes + N);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
|
||||||
void set_supported_custom_fan_modes(const std::vector<std::string> &modes) = delete;
|
|
||||||
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) = delete;
|
|
||||||
|
|
||||||
const std::vector<const char *> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
|
||||||
bool supports_custom_fan_mode(const char *custom_fan_mode) const {
|
|
||||||
return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
|
|
||||||
}
|
}
|
||||||
|
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
||||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||||
return this->supports_custom_fan_mode(custom_fan_mode.c_str());
|
return this->supported_custom_fan_modes_.count(custom_fan_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
|
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
|
||||||
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
|
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
|
||||||
|
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
|
||||||
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
|
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
|
||||||
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
||||||
const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
|
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
|
||||||
|
|
||||||
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
|
||||||
this->supported_custom_presets_ = presets;
|
this->supported_custom_presets_ = std::move(supported_custom_presets);
|
||||||
}
|
|
||||||
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
|
||||||
this->supported_custom_presets_ = presets;
|
|
||||||
}
|
|
||||||
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
|
||||||
this->supported_custom_presets_.assign(presets, presets + N);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
|
||||||
void set_supported_custom_presets(const std::vector<std::string> &presets) = delete;
|
|
||||||
void set_supported_custom_presets(std::initializer_list<std::string> presets) = delete;
|
|
||||||
|
|
||||||
const std::vector<const char *> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
|
||||||
bool supports_custom_preset(const char *custom_preset) const {
|
|
||||||
return vector_contains(this->supported_custom_presets_, custom_preset);
|
|
||||||
}
|
}
|
||||||
|
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
||||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||||
return this->supports_custom_preset(custom_preset.c_str());
|
return this->supported_custom_presets_.count(custom_preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
|
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
|
||||||
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
|
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||||
|
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||||
|
void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||||
|
void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); }
|
||||||
|
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||||
|
void set_supports_swing_mode_horizontal(bool supported) {
|
||||||
|
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
|
||||||
|
}
|
||||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
|
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
|
||||||
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
|
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
|
||||||
const ClimateSwingModeMask &get_supported_swing_modes() const { return this->supported_swing_modes_; }
|
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
|
||||||
|
|
||||||
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
|
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
|
||||||
void set_visual_min_temperature(float visual_min_temperature) {
|
void set_visual_min_temperature(float visual_min_temperature) {
|
||||||
@@ -233,6 +221,23 @@ class ClimateTraits {
|
|||||||
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
|
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
#ifdef USE_API
|
||||||
|
// The API connection is a friend class to access internal methods
|
||||||
|
friend class api::APIConnection;
|
||||||
|
// These methods return references to internal data structures.
|
||||||
|
// They are used by the API to avoid copying data when encoding messages.
|
||||||
|
// Warning: Do not use these methods outside of the API connection code.
|
||||||
|
// They return references to internal data that can be invalidated.
|
||||||
|
const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; }
|
||||||
|
const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; }
|
||||||
|
const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const {
|
||||||
|
return this->supported_custom_fan_modes_;
|
||||||
|
}
|
||||||
|
const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; }
|
||||||
|
const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; }
|
||||||
|
const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; }
|
||||||
|
#endif
|
||||||
|
|
||||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
this->supported_modes_.insert(mode);
|
this->supported_modes_.insert(mode);
|
||||||
@@ -255,18 +260,6 @@ class ClimateTraits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
|
|
||||||
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
|
|
||||||
const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
|
|
||||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
|
||||||
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
|
|
||||||
const char *find_custom_preset_(const char *custom_preset) const {
|
|
||||||
return vector_find(this->supported_custom_presets_, custom_preset);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t feature_flags_{0};
|
uint32_t feature_flags_{0};
|
||||||
float visual_min_temperature_{10};
|
float visual_min_temperature_{10};
|
||||||
float visual_max_temperature_{30};
|
float visual_max_temperature_{30};
|
||||||
@@ -275,21 +268,12 @@ class ClimateTraits {
|
|||||||
float visual_min_humidity_{30};
|
float visual_min_humidity_{30};
|
||||||
float visual_max_humidity_{99};
|
float visual_max_humidity_{99};
|
||||||
|
|
||||||
climate::ClimateModeMask supported_modes_{climate::CLIMATE_MODE_OFF};
|
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
||||||
climate::ClimateFanModeMask supported_fan_modes_;
|
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
||||||
climate::ClimateSwingModeMask supported_swing_modes_;
|
std::set<climate::ClimateSwingMode> supported_swing_modes_;
|
||||||
climate::ClimatePresetMask supported_presets_;
|
std::set<climate::ClimatePreset> supported_presets_;
|
||||||
|
std::set<std::string> supported_custom_fan_modes_;
|
||||||
/** Custom mode storage using const char* pointers to eliminate std::string overhead.
|
std::set<std::string> supported_custom_presets_;
|
||||||
*
|
|
||||||
* Pointers must remain valid for the ClimateTraits lifetime. Safe patterns:
|
|
||||||
* - String literals: set_supported_custom_fan_modes({"Turbo", "Silent"})
|
|
||||||
* - Static const data: static const char* MODE = "Eco";
|
|
||||||
*
|
|
||||||
* Climate class setters validate pointers are from these vectors before storing.
|
|
||||||
*/
|
|
||||||
std::vector<const char *> supported_custom_fan_modes_;
|
|
||||||
std::vector<const char *> supported_custom_presets_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from esphome import core
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import climate, remote_base, sensor
|
from esphome.components import climate, remote_base, sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
|
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -51,6 +52,26 @@ def climate_ir_with_receiver_schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
def deprecated_schema_constant(config):
|
||||||
|
type: str = "unknown"
|
||||||
|
if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
|
||||||
|
type = str(id.type).split("::", maxsplit=1)[0]
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
|
||||||
|
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
|
||||||
|
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
|
||||||
|
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
|
||||||
|
"Component using this schema: %s",
|
||||||
|
type,
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
|
||||||
|
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
|
||||||
|
|
||||||
|
|
||||||
async def register_climate_ir(var, config):
|
async def register_climate_ir(var, config):
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await remote_base.register_transmittable(var, config)
|
await remote_base.register_transmittable(var, config)
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ static const char *const TAG = "climate_ir";
|
|||||||
|
|
||||||
climate::ClimateTraits ClimateIR::traits() {
|
climate::ClimateTraits ClimateIR::traits() {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
if (this->sensor_ != nullptr) {
|
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||||
if (this->supports_cool_)
|
if (this->supports_cool_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||||
@@ -22,6 +19,7 @@ climate::ClimateTraits ClimateIR::traits() {
|
|||||||
if (this->supports_fan_only_)
|
if (this->supports_fan_only_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||||
|
|
||||||
|
traits.set_supports_two_point_target_temperature(false);
|
||||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||||
traits.set_visual_temperature_step(this->temperature_step_);
|
traits.set_visual_temperature_step(this->temperature_step_);
|
||||||
|
|||||||
@@ -24,18 +24,16 @@ class ClimateIR : public Component,
|
|||||||
public remote_base::RemoteTransmittable {
|
public remote_base::RemoteTransmittable {
|
||||||
public:
|
public:
|
||||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||||
bool supports_dry = false, bool supports_fan_only = false,
|
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
||||||
climate::ClimateFanModeMask fan_modes = climate::ClimateFanModeMask(),
|
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
|
||||||
climate::ClimateSwingModeMask swing_modes = climate::ClimateSwingModeMask(),
|
|
||||||
climate::ClimatePresetMask presets = climate::ClimatePresetMask()) {
|
|
||||||
this->minimum_temperature_ = minimum_temperature;
|
this->minimum_temperature_ = minimum_temperature;
|
||||||
this->maximum_temperature_ = maximum_temperature;
|
this->maximum_temperature_ = maximum_temperature;
|
||||||
this->temperature_step_ = temperature_step;
|
this->temperature_step_ = temperature_step;
|
||||||
this->supports_dry_ = supports_dry;
|
this->supports_dry_ = supports_dry;
|
||||||
this->supports_fan_only_ = supports_fan_only;
|
this->supports_fan_only_ = supports_fan_only;
|
||||||
this->fan_modes_ = fan_modes;
|
this->fan_modes_ = std::move(fan_modes);
|
||||||
this->swing_modes_ = swing_modes;
|
this->swing_modes_ = std::move(swing_modes);
|
||||||
this->presets_ = presets;
|
this->presets_ = std::move(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
@@ -62,9 +60,9 @@ class ClimateIR : public Component,
|
|||||||
bool supports_heat_{true};
|
bool supports_heat_{true};
|
||||||
bool supports_dry_{false};
|
bool supports_dry_{false};
|
||||||
bool supports_fan_only_{false};
|
bool supports_fan_only_{false};
|
||||||
climate::ClimateFanModeMask fan_modes_{};
|
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
||||||
climate::ClimateSwingModeMask swing_modes_{};
|
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
||||||
climate::ClimatePresetMask presets_{};
|
std::set<climate::ClimatePreset> presets_ = {};
|
||||||
|
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ template<typename... Ts> class CM1106CalibrateZeroAction : public Action<Ts...>
|
|||||||
public:
|
public:
|
||||||
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
|
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->cm1106_->calibrate_zero(400); }
|
void play(Ts... x) override { this->cm1106_->calibrate_zero(400); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CM1106Component *cm1106_;
|
CM1106Component *cm1106_;
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ BYTE_ORDER_BIG = "big_endian"
|
|||||||
|
|
||||||
CONF_COLOR_DEPTH = "color_depth"
|
CONF_COLOR_DEPTH = "color_depth"
|
||||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||||
CONF_ENABLED = "enabled"
|
|
||||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
|
||||||
CONF_ON_RECEIVE = "on_receive"
|
CONF_ON_RECEIVE = "on_receive"
|
||||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||||
CONF_REQUEST_HEADERS = "request_headers"
|
CONF_REQUEST_HEADERS = "request_headers"
|
||||||
CONF_ROWS = "rows"
|
|
||||||
CONF_USE_PSRAM = "use_psram"
|
CONF_USE_PSRAM = "use_psram"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ void CopyFan::setup() {
|
|||||||
this->oscillating = source_->oscillating;
|
this->oscillating = source_->oscillating;
|
||||||
this->speed = source_->speed;
|
this->speed = source_->speed;
|
||||||
this->direction = source_->direction;
|
this->direction = source_->direction;
|
||||||
this->set_preset_mode_(source_->get_preset_mode());
|
this->preset_mode = source_->preset_mode;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ void CopyFan::setup() {
|
|||||||
this->oscillating = source_->oscillating;
|
this->oscillating = source_->oscillating;
|
||||||
this->speed = source_->speed;
|
this->speed = source_->speed;
|
||||||
this->direction = source_->direction;
|
this->direction = source_->direction;
|
||||||
this->set_preset_mode_(source_->get_preset_mode());
|
this->preset_mode = source_->preset_mode;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ void CopyFan::control(const fan::FanCall &call) {
|
|||||||
call2.set_speed(*call.get_speed());
|
call2.set_speed(*call.get_speed());
|
||||||
if (call.get_direction().has_value())
|
if (call.get_direction().has_value())
|
||||||
call2.set_direction(*call.get_direction());
|
call2.set_direction(*call.get_direction());
|
||||||
if (call.has_preset_mode())
|
if (!call.get_preset_mode().empty())
|
||||||
call2.set_preset_mode(call.get_preset_mode());
|
call2.set_preset_mode(call.get_preset_mode());
|
||||||
call2.perform();
|
call2.perform();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ namespace copy {
|
|||||||
static const char *const TAG = "copy.select";
|
static const char *const TAG = "copy.select";
|
||||||
|
|
||||||
void CopySelect::setup() {
|
void CopySelect::setup() {
|
||||||
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); });
|
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); });
|
||||||
|
|
||||||
traits.set_options(source_->traits.get_options());
|
traits.set_options(source_->traits.get_options());
|
||||||
|
|
||||||
if (source_->has_state())
|
if (source_->has_state())
|
||||||
this->publish_state(source_->active_index().value());
|
this->publish_state(source_->state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
||||||
|
|
||||||
void CopySelect::control(size_t index) {
|
void CopySelect::control(const std::string &value) {
|
||||||
auto call = source_->make_call();
|
auto call = source_->make_call();
|
||||||
call.set_index(index);
|
call.set_option(value);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class CopySelect : public select::Select, public Component {
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void control(size_t index) override;
|
void control(const std::string &value) override;
|
||||||
|
|
||||||
select::Select *source_;
|
select::Select *source_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -151,6 +151,11 @@ def cover_schema(
|
|||||||
return _COVER_SCHEMA.extend(schema)
|
return _COVER_SCHEMA.extend(schema)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove before 2025.11.0
|
||||||
|
COVER_SCHEMA = cover_schema(Cover)
|
||||||
|
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
|
||||||
|
|
||||||
|
|
||||||
async def setup_cover_core_(var, config):
|
async def setup_cover_core_(var, config):
|
||||||
await setup_entity(var, config, "cover")
|
await setup_entity(var, config, "cover")
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ template<typename... Ts> class OpenAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit OpenAction(Cover *cover) : cover_(cover) {}
|
explicit OpenAction(Cover *cover) : cover_(cover) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->cover_->make_call().set_command_open().perform(); }
|
void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
@@ -21,7 +21,7 @@ template<typename... Ts> class CloseAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit CloseAction(Cover *cover) : cover_(cover) {}
|
explicit CloseAction(Cover *cover) : cover_(cover) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->cover_->make_call().set_command_close().perform(); }
|
void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
@@ -31,7 +31,7 @@ template<typename... Ts> class StopAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit StopAction(Cover *cover) : cover_(cover) {}
|
explicit StopAction(Cover *cover) : cover_(cover) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->cover_->make_call().set_command_stop().perform(); }
|
void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
@@ -41,7 +41,7 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit ToggleAction(Cover *cover) : cover_(cover) {}
|
explicit ToggleAction(Cover *cover) : cover_(cover) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { this->cover_->make_call().set_command_toggle().perform(); }
|
void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
@@ -55,7 +55,7 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||||||
TEMPLATABLE_VALUE(float, position)
|
TEMPLATABLE_VALUE(float, position)
|
||||||
TEMPLATABLE_VALUE(float, tilt)
|
TEMPLATABLE_VALUE(float, tilt)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->cover_->make_call();
|
auto call = this->cover_->make_call();
|
||||||
if (this->stop_.has_value())
|
if (this->stop_.has_value())
|
||||||
call.set_stop(this->stop_.value(x...));
|
call.set_stop(this->stop_.value(x...));
|
||||||
@@ -77,7 +77,7 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
|||||||
TEMPLATABLE_VALUE(float, tilt)
|
TEMPLATABLE_VALUE(float, tilt)
|
||||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
if (this->position_.has_value())
|
if (this->position_.has_value())
|
||||||
this->cover_->position = this->position_.value(x...);
|
this->cover_->position = this->position_.value(x...);
|
||||||
if (this->tilt_.has_value())
|
if (this->tilt_.has_value())
|
||||||
@@ -94,7 +94,7 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
|||||||
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
|
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
|
||||||
bool check(const Ts &...x) override { return this->cover_->is_fully_open(); }
|
bool check(Ts... x) override { return this->cover_->is_fully_open(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
@@ -103,7 +103,7 @@ template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
|||||||
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
|
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
|
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
|
||||||
bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); }
|
bool check(Ts... x) override { return this->cover_->is_fully_closed(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
#include "cover.h"
|
#include "cover.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
|
|
||||||
#include <strings.h>
|
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace cover {
|
namespace cover {
|
||||||
@@ -148,7 +144,21 @@ CoverCall &CoverCall::set_stop(bool stop) {
|
|||||||
bool CoverCall::get_stop() const { return this->stop_; }
|
bool CoverCall::get_stop() const { return this->stop_; }
|
||||||
|
|
||||||
CoverCall Cover::make_call() { return {this}; }
|
CoverCall Cover::make_call() { return {this}; }
|
||||||
|
void Cover::open() {
|
||||||
|
auto call = this->make_call();
|
||||||
|
call.set_command_open();
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
void Cover::close() {
|
||||||
|
auto call = this->make_call();
|
||||||
|
call.set_command_close();
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
void Cover::stop() {
|
||||||
|
auto call = this->make_call();
|
||||||
|
call.set_command_stop();
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
|
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
|
||||||
void Cover::publish_state(bool save) {
|
void Cover::publish_state(bool save) {
|
||||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||||
@@ -173,9 +183,6 @@ void Cover::publish_state(bool save) {
|
|||||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||||
|
|
||||||
this->state_callback_.call();
|
this->state_callback_.call();
|
||||||
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
|
|
||||||
ControllerRegistry::notify_cover_update(this);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
CoverRestoreState restore{};
|
CoverRestoreState restore{};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
#include "cover_traits.h"
|
#include "cover_traits.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -126,6 +125,25 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
|||||||
|
|
||||||
/// Construct a new cover call used to control the cover.
|
/// Construct a new cover call used to control the cover.
|
||||||
CoverCall make_call();
|
CoverCall make_call();
|
||||||
|
/** Open the cover.
|
||||||
|
*
|
||||||
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
|
*/
|
||||||
|
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open().perform() instead.", "2021.9")
|
||||||
|
void open();
|
||||||
|
/** Close the cover.
|
||||||
|
*
|
||||||
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
|
*/
|
||||||
|
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close().perform() instead.", "2021.9")
|
||||||
|
void close();
|
||||||
|
/** Stop the cover.
|
||||||
|
*
|
||||||
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
|
* As per solution from issue #2885 the call should include perform()
|
||||||
|
*/
|
||||||
|
ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop().perform() instead.", "2021.9")
|
||||||
|
void stop();
|
||||||
|
|
||||||
void add_on_state_callback(std::function<void()> &&f);
|
void add_on_state_callback(std::function<void()> &&f);
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ template<typename... Ts> class CS5460ARestartAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}
|
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}
|
||||||
|
|
||||||
void play(const Ts &...x) override { cs5460a_->restart(); }
|
void play(Ts... x) override { cs5460a_->restart(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CS5460AComponent *cs5460a_;
|
CS5460AComponent *cs5460a_;
|
||||||
|
|||||||
@@ -241,7 +241,9 @@ uint8_t DaikinArcClimate::humidity_() {
|
|||||||
|
|
||||||
climate::ClimateTraits DaikinArcClimate::traits() {
|
climate::ClimateTraits DaikinArcClimate::traits() {
|
||||||
climate::ClimateTraits traits = climate_ir::ClimateIR::traits();
|
climate::ClimateTraits traits = climate_ir::ClimateIR::traits();
|
||||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE | climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY);
|
traits.set_supports_current_temperature(true);
|
||||||
|
traits.set_supports_current_humidity(false);
|
||||||
|
traits.set_supports_target_humidity(true);
|
||||||
traits.set_visual_min_humidity(38);
|
traits.set_visual_min_humidity(38);
|
||||||
traits.set_visual_max_humidity(52);
|
traits.set_visual_max_humidity(52);
|
||||||
return traits;
|
return traits;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DallasTemperatureSensor::setup() {
|
void DallasTemperatureSensor::setup() {
|
||||||
if (!this->check_address_or_index_())
|
if (!this->check_address_())
|
||||||
return;
|
return;
|
||||||
if (!this->read_scratch_pad_())
|
if (!this->read_scratch_pad_())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "date_entity.h"
|
#include "date_entity.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -33,9 +32,6 @@ void DateEntity::publish_state() {
|
|||||||
this->set_has_state(true);
|
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_);
|
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||||
this->state_callback_.call();
|
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); }
|
DateCall DateEntity::make_call() { return DateCall(this); }
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ template<typename... Ts> class DateSetAction : public Action<Ts...>, public Pare
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(ESPTime, date)
|
TEMPLATABLE_VALUE(ESPTime, date)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
|
|
||||||
if (this->date_.has_value()) {
|
if (this->date_.has_value()) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "datetime_entity.h"
|
#include "datetime_entity.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#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_,
|
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->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||||
this->state_callback_.call();
|
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); }
|
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ template<typename... Ts> class DateTimeSetAction : public Action<Ts...>, public
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(ESPTime, datetime)
|
TEMPLATABLE_VALUE(ESPTime, datetime)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
|
|
||||||
if (this->datetime_.has_value()) {
|
if (this->datetime_.has_value()) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "time_entity.h"
|
#include "time_entity.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/controller_registry.h"
|
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#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_,
|
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
||||||
this->second_);
|
this->second_);
|
||||||
this->state_callback_.call();
|
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); }
|
TimeCall TimeEntity::make_call() { return TimeCall(this); }
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user