mirror of
https://github.com/esphome/esphome.git
synced 2025-11-06 10:01:51 +00:00
Compare commits
120 Commits
max6956_gp
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26607713bb | ||
|
|
895d76ca03 | ||
|
|
74187845b7 | ||
|
|
822eacfd77 | ||
|
|
ab5d8f67ae | ||
|
|
83f30a64ed | ||
|
|
5eea7bdb44 | ||
|
|
bdfd88441a | ||
|
|
20b6e0d5c2 | ||
|
|
ce5e608863 | ||
|
|
aa5795c019 | ||
|
|
00c0854323 | ||
|
|
be006ecadd | ||
|
|
b08419fa47 | ||
|
|
d36ef050a9 | ||
|
|
df53ff7afe | ||
|
|
b7838671ae | ||
|
|
479f8dd85c | ||
|
|
6e2dbbf636 | ||
|
|
6b522dfee6 | ||
|
|
32975c9d8b | ||
|
|
1446e7174a | ||
|
|
64f8963566 | ||
|
|
6f7e54c3f3 | ||
|
|
c7ae424613 | ||
|
|
c5e5609e92 | ||
|
|
885508775f | ||
|
|
531b27582a | ||
|
|
aed7505f53 | ||
|
|
191a88c2dc | ||
|
|
968df6cb3f | ||
|
|
71fa88c9d4 | ||
|
|
84f7cacef9 | ||
|
|
13e3c03a61 | ||
|
|
060bb4159f | ||
|
|
980098ca77 | ||
|
|
4d2f9db861 | ||
|
|
4c31cb57ea | ||
|
|
5257900495 | ||
|
|
3e086c2127 | ||
|
|
0b04361fc0 | ||
|
|
758ac58343 | ||
|
|
ce63137565 | ||
|
|
00155989af | ||
|
|
326975ccad | ||
|
|
6220084fe6 | ||
|
|
59326f137e | ||
|
|
266e4ae91f | ||
|
|
99d1a9cf6e | ||
|
|
99ce989eae | ||
|
|
a3583da17d | ||
|
|
0f6fd91304 | ||
|
|
2f5f1da16f | ||
|
|
51745d1d5e | ||
|
|
fecc8399a5 | ||
|
|
db395a662d | ||
|
|
641dd24b21 | ||
|
|
57f2e32b00 | ||
|
|
8aa8bb8f98 | ||
|
|
9c7cb30ae5 | ||
|
|
fb7dbc9910 | ||
|
|
3f12630a6b | ||
|
|
06d0787ee0 | ||
|
|
cb039b42aa | ||
|
|
f05f45af74 | ||
|
|
1ec1692c77 | ||
|
|
7e1cea8e69 | ||
|
|
0e792d0791 | ||
|
|
42833c85f5 | ||
|
|
a41c7b2b3c | ||
|
|
4dd3c90663 | ||
|
|
0f0cd1f706 | ||
|
|
4a5e6576c8 | ||
|
|
cf76c3a747 | ||
|
|
3f05fd82e5 | ||
|
|
34244afea1 | ||
|
|
4838eff382 | ||
|
|
712421b82b | ||
|
|
7a1297ec84 | ||
|
|
40f919eaa6 | ||
|
|
01ae86145a | ||
|
|
17ab20ef61 | ||
|
|
1509ed8d23 | ||
|
|
3e17767f6a | ||
|
|
19e275dc02 | ||
|
|
86402be9e3 | ||
|
|
8a8a80e107 | ||
|
|
79378a930e | ||
|
|
c822ec152f | ||
|
|
50e7ce55e7 | ||
|
|
70ea3af578 | ||
|
|
338190abec | ||
|
|
425c88ee94 | ||
|
|
f6946c0b9a | ||
|
|
edde2fc94c | ||
|
|
1fc3165b58 | ||
|
|
d25121a55c | ||
|
|
55af818629 | ||
|
|
c662697ca7 | ||
|
|
e28c152298 | ||
|
|
0b4d445794 | ||
|
|
4d1d37a911 | ||
|
|
8df5a3a630 | ||
|
|
5a5894eaa3 | ||
|
|
d9d2d2f6b9 | ||
|
|
30f2a4395f | ||
|
|
292abd1187 | ||
|
|
6d0527ff2a | ||
|
|
fd64585f99 | ||
|
|
077cce9848 | ||
|
|
bd87e56bc7 | ||
|
|
58235049e3 | ||
|
|
29ed3c20af | ||
|
|
08aae39ea4 | ||
|
|
03fd114371 | ||
|
|
918650f15a | ||
|
|
287f65cbaf | ||
|
|
f18c70a256 | ||
|
|
6fb490f49b | ||
|
|
66cf7c3a3b |
@@ -51,7 +51,79 @@ 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.
|
* **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions):
|
||||||
|
- Function, method, and variable names: `lower_snake_case`
|
||||||
|
- Class/struct/enum names: `UpperCamelCase`
|
||||||
|
- Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE`
|
||||||
|
- Function-local constants: `lower_snake_case`
|
||||||
|
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
|
||||||
|
- Favor descriptive names over abbreviations
|
||||||
|
|
||||||
|
* **C++ Field Visibility:**
|
||||||
|
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
|
||||||
|
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
|
||||||
|
1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references.
|
||||||
|
```cpp
|
||||||
|
// Helper to find matching string in vector and return its pointer
|
||||||
|
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
|
||||||
|
for (const char *item : vec) {
|
||||||
|
if (strcmp(item, value) == 0)
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClimateDevice {
|
||||||
|
public:
|
||||||
|
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||||
|
this->custom_fan_modes_ = modes;
|
||||||
|
this->active_custom_fan_mode_ = nullptr; // Reset when modes change
|
||||||
|
}
|
||||||
|
bool set_custom_fan_mode(const char *mode) {
|
||||||
|
// Find mode in supported list and store that pointer (not the input pointer)
|
||||||
|
const char *validated_mode = vector_find(this->custom_fan_modes_, mode);
|
||||||
|
if (validated_mode != nullptr) {
|
||||||
|
this->active_custom_fan_mode_ = validated_mode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<const char *> custom_fan_modes_; // Pointers to string literals in flash
|
||||||
|
const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_
|
||||||
|
};
|
||||||
|
```
|
||||||
|
2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption.
|
||||||
|
```cpp
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
void resize(size_t new_size) {
|
||||||
|
auto new_data = std::make_unique<uint8_t[]>(new_size);
|
||||||
|
if (this->data_) {
|
||||||
|
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
|
||||||
|
}
|
||||||
|
this->data_ = std::move(new_data);
|
||||||
|
this->size_ = new_size; // Must stay in sync with data_
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unique_ptr<uint8_t[]> data_;
|
||||||
|
size_t size_{0}; // Must match allocated size of data_
|
||||||
|
};
|
||||||
|
```
|
||||||
|
3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip.
|
||||||
|
* **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members.
|
||||||
|
|
||||||
|
* **C++ Preprocessor Directives:**
|
||||||
|
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
|
||||||
|
* **Use `#define` only for:**
|
||||||
|
- Conditional compilation (`#ifdef`, `#ifndef`)
|
||||||
|
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
|
||||||
|
|
||||||
|
* **C++ Additional Conventions:**
|
||||||
|
* **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`)
|
||||||
|
* **Indentation:** Use spaces (two per indentation level), not tabs
|
||||||
|
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
|
||||||
|
* **Line length:** Wrap lines at no more than 120 characters
|
||||||
|
|
||||||
* **Component Structure:**
|
* **Component Structure:**
|
||||||
* **Standard Files:**
|
* **Standard Files:**
|
||||||
|
|||||||
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@@ -180,6 +180,7 @@ jobs:
|
|||||||
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-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
||||||
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
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
|
||||||
@@ -191,6 +192,11 @@ 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:
|
||||||
@@ -214,6 +220,13 @@ jobs:
|
|||||||
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-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 "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
|
||||||
@@ -458,7 +471,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 1
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- id: clang-tidy
|
- id: clang-tidy
|
||||||
@@ -536,59 +549,18 @@ jobs:
|
|||||||
run: script/ci-suggest-changes
|
run: script/ci-suggest-changes
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
test-build-components-splitter:
|
|
||||||
name: Split components for intelligent grouping (40 weighted per batch)
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- determine-jobs
|
|
||||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
|
||||||
outputs:
|
|
||||||
matrix: ${{ steps.split.outputs.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: Split components intelligently based on bus configurations
|
|
||||||
id: split
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
|
|
||||||
# Use intelligent splitter that groups components with same bus configs
|
|
||||||
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
|
|
||||||
directly_changed='${{ needs.determine-jobs.outputs.directly-changed-components-with-tests }}'
|
|
||||||
echo "Target branch: ${{ github.base_ref }} - isolating directly changed components"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Splitting components intelligently..."
|
|
||||||
output=$(python3 script/split_components_for_ci.py --components "$components" --directly-changed "$directly_changed" --batch-size 40 --output github)
|
|
||||||
|
|
||||||
echo "$output" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
test-build-components-split:
|
test-build-components-split:
|
||||||
name: Test components batch (${{ matrix.components }})
|
name: Test components batch (${{ matrix.components }})
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
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.test-build-components-splitter.outputs.matrix) }}
|
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Show disk space
|
- name: Show disk space
|
||||||
run: |
|
run: |
|
||||||
@@ -980,7 +952,6 @@ jobs:
|
|||||||
- clang-tidy-nosplit
|
- clang-tidy-nosplit
|
||||||
- clang-tidy-split
|
- 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
|
||||||
|
|||||||
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@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||||
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@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
@@ -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.2
|
rev: v0.14.3
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ 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
|
||||||
@@ -180,7 +181,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
|
esphome/components/gp8403/* @jesserockz @sebydocky
|
||||||
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
|
||||||
@@ -479,6 +480,7 @@ 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
|
||||||
|
|||||||
@@ -207,14 +207,14 @@ 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():
|
if has_api() and has_non_ip_address() and has_resolvable_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():
|
if has_ota() and has_non_ip_address() and has_resolvable_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)
|
||||||
@@ -318,7 +318,17 @@ 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
|
||||||
return CORE.address is not None
|
if CORE.address is 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):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from esphome.const import (
|
|||||||
CONF_TYPE_ID,
|
CONF_TYPE_ID,
|
||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
from esphome.core import ID
|
from esphome.core import ID, Lambda
|
||||||
from esphome.cpp_generator import (
|
from esphome.cpp_generator import (
|
||||||
LambdaExpression,
|
LambdaExpression,
|
||||||
MockObj,
|
MockObj,
|
||||||
@@ -182,7 +182,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("Cannot have more than 1 automation for templates")
|
raise cv.Invalid("This trigger allows only a single automation")
|
||||||
return value[0]
|
return value[0]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -310,6 +310,30 @@ 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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(Ts... x) override { this->parent_->new_i2c_address(this->new_address_.value(x...)); }
|
void play(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
void play(const Ts &...x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AIC3204 *aic3204_;
|
AIC3204 *aic3204_;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(std::string, code)
|
TEMPLATABLE_VALUE(std::string, code)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override { this->alarm_control_panel_->disarm(this->code_.optional_value(x...)); }
|
void play(const 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(Ts... x) override { this->alarm_control_panel_->make_call().pending().perform(); }
|
void play(const 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(Ts... x) override { this->alarm_control_panel_->make_call().triggered().perform(); }
|
void play(const 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(Ts... x) override {
|
bool check(const 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(Ts... x) override { this->parent_->next_frame(); }
|
void play(const 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(Ts... x) override { this->parent_->prev_frame(); }
|
void play(const 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(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); }
|
void play(const Ts &...x) override { this->parent_->set_frame(this->frame_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Animation *parent_;
|
Animation *parent_;
|
||||||
|
|||||||
@@ -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) = "std::set"];
|
repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||||
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
|
||||||
@@ -1000,9 +1000,9 @@ message ListEntitiesClimateResponse {
|
|||||||
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_no_template) = "climate::ClimateFanModeMask"];
|
||||||
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
|
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
|
||||||
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::vector"];
|
repeated string supported_custom_fan_modes = 15 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||||
repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
|
repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
|
||||||
repeated string supported_custom_presets = 17 [(container_pointer) = "std::vector"];
|
repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||||
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;
|
||||||
|
|||||||
@@ -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())
|
if (traits.supports_preset_modes() && fan->has_preset_mode())
|
||||||
msg.set_preset_mode(StringRef(fan->preset_mode));
|
msg.set_preset_mode(StringRef(fan->get_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_for_api_();
|
msg.supported_preset_modes = &traits.supported_preset_modes();
|
||||||
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) {
|
||||||
@@ -637,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->custom_fan_mode.has_value()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
|
||||||
resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
|
resp.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode()));
|
||||||
}
|
}
|
||||||
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->custom_preset.has_value()) {
|
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
|
||||||
resp.set_custom_preset(StringRef(climate->custom_preset.value()));
|
resp.set_custom_preset(StringRef(climate->get_custom_preset()));
|
||||||
}
|
}
|
||||||
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);
|
||||||
@@ -877,7 +877,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->state));
|
resp.set_state(StringRef(select->current_option()));
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,8 +434,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||||
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,8 +230,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||||
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());
|
||||||
|
|||||||
@@ -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 auto &it : *this->supported_preset_modes) {
|
for (const char *it : *this->supported_preset_modes) {
|
||||||
buffer.encode_string(12, it, true);
|
buffer.encode_string(12, it, strlen(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 auto &it : *this->supported_preset_modes) {
|
for (const char *it : *this->supported_preset_modes) {
|
||||||
size.add_length_force(1, it.size());
|
size.add_length_force(1, strlen(it));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#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 auto &it : *this->supported_custom_fan_modes) {
|
for (const char *it : *this->supported_custom_fan_modes) {
|
||||||
buffer.encode_string(15, it, true);
|
buffer.encode_string(15, it, strlen(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 auto &it : *this->supported_custom_presets) {
|
for (const char *it : *this->supported_custom_presets) {
|
||||||
buffer.encode_string(17, it, true);
|
buffer.encode_string(17, it, strlen(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 auto &it : *this->supported_custom_fan_modes) {
|
for (const char *it : *this->supported_custom_fan_modes) {
|
||||||
size.add_length_force(1, it.size());
|
size.add_length_force(1, strlen(it));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 auto &it : *this->supported_custom_presets) {
|
for (const char *it : *this->supported_custom_presets) {
|
||||||
size.add_length_force(2, it.size());
|
size.add_length_force(2, strlen(it));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size.add_bool(2, this->disabled_by_default);
|
size.add_bool(2, this->disabled_by_default);
|
||||||
|
|||||||
@@ -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::set<std::string> *supported_preset_modes{};
|
const std::vector<const char *> *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
|
||||||
@@ -1384,9 +1384,9 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
|||||||
bool supports_action{false};
|
bool supports_action{false};
|
||||||
const climate::ClimateFanModeMask *supported_fan_modes{};
|
const climate::ClimateFanModeMask *supported_fan_modes{};
|
||||||
const climate::ClimateSwingModeMask *supported_swing_modes{};
|
const climate::ClimateSwingModeMask *supported_swing_modes{};
|
||||||
const std::vector<std::string> *supported_custom_fan_modes{};
|
const std::vector<const char *> *supported_custom_fan_modes{};
|
||||||
const climate::ClimatePresetMask *supported_presets{};
|
const climate::ClimatePresetMask *supported_presets{};
|
||||||
const std::vector<std::string> *supported_custom_presets{};
|
const std::vector<const char *> *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};
|
||||||
|
|||||||
@@ -224,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().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
|
network::get_use_address(), 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()) {
|
||||||
|
|||||||
@@ -237,7 +237,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(Ts... x) override { return global_api_server->is_connected(); }
|
bool check(const 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 UserServiceBase<Ts...> {
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<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...))
|
||||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
: UserServiceDynamic<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
|
||||||
|
|||||||
@@ -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(Ts... x) override {
|
void play(const 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));
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ 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(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
|
||||||
: name_(std::move(name)), arg_names_(arg_names) {
|
: name_(name), arg_names_(arg_names) {
|
||||||
this->key_ = fnv1_hash(this->name_);
|
this->key_ = fnv1_hash(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||||
@@ -47,7 +49,7 @@ template<typename... Ts> class UserServiceBase : 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() != this->arg_names_.size())
|
if (req.args.size() != sizeof...(Ts))
|
||||||
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;
|
||||||
@@ -59,14 +61,60 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name_;
|
// Pointers to string literals in flash - no heap allocation
|
||||||
|
const char *name_;
|
||||||
|
std::array<const char *, sizeof...(Ts)> arg_names_;
|
||||||
uint32_t key_{0};
|
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) {
|
||||||
|
this->key_ = fnv1_hash(this->name_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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]))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heap-allocated strings for runtime-generated names
|
||||||
|
std::string name_;
|
||||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||||
|
uint32_t key_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||||
public:
|
public:
|
||||||
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
// Constructor for static names (YAML-defined services - used by code generator)
|
||||||
|
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(Ts... x) { this->parent_->reset_hardware_frontend(); }
|
void play(const 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(Ts... x) {
|
void play(const 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(Ts... x) override { this->audio_adc_->set_mic_gain(this->mic_gain_.value(x...)); }
|
void play(const 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(Ts... x) override { this->audio_dac_->set_mute_off(); }
|
void play(const 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(Ts... x) override { this->audio_dac_->set_mute_on(); }
|
void play(const 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(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
void play(const Ts &...x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioDac *audio_dac_;
|
AudioDac *audio_dac_;
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ 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 constexpr 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_;
|
|
||||||
|
|
||||||
} // namespace bedjet
|
} // namespace bedjet
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace bedjet {
|
|||||||
|
|
||||||
using namespace esphome::climate;
|
using namespace esphome::climate;
|
||||||
|
|
||||||
static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
|
static const char *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_NAME_STRINGS[fan_step];
|
return BEDJET_FAN_STEP_NAMES[fan_step];
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) {
|
static uint8_t bedjet_fan_speed_to_step(const char *fan_step_percent) {
|
||||||
for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) {
|
for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) {
|
||||||
if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) {
|
if (strcmp(BEDJET_FAN_STEP_NAMES[i], fan_step_percent) == 0) {
|
||||||
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.c_str());
|
ESP_LOGCONFIG(TAG, " - %s (c)", mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.c_str());
|
ESP_LOGCONFIG(TAG, " - %s (c)", preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,8 +144,7 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->mode = CLIMATE_MODE_HEAT;
|
this->mode = CLIMATE_MODE_HEAT;
|
||||||
this->preset = CLIMATE_PRESET_BOOST;
|
this->set_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) {
|
||||||
@@ -153,7 +152,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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
}
|
}
|
||||||
} 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'",
|
||||||
@@ -164,28 +163,27 @@ 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.get_custom_preset().has_value()) {
|
} else if (call.has_custom_preset()) {
|
||||||
std::string preset = *call.get_custom_preset();
|
const char *preset = call.get_custom_preset();
|
||||||
bool result;
|
bool result;
|
||||||
|
|
||||||
if (preset == "M1") {
|
if (strcmp(preset, "M1") == 0) {
|
||||||
result = this->parent_->button_memory1();
|
result = this->parent_->button_memory1();
|
||||||
} else if (preset == "M2") {
|
} else if (strcmp(preset, "M2") == 0) {
|
||||||
result = this->parent_->button_memory2();
|
result = this->parent_->button_memory2();
|
||||||
} else if (preset == "M3") {
|
} else if (strcmp(preset, "M3") == 0) {
|
||||||
result = this->parent_->button_memory3();
|
result = this->parent_->button_memory3();
|
||||||
} else if (preset == "LTD HT") {
|
} else if (strcmp(preset, "LTD HT") == 0) {
|
||||||
result = this->parent_->button_heat();
|
result = this->parent_->button_heat();
|
||||||
} else if (preset == "EXT HT") {
|
} else if (strcmp(preset, "EXT HT") == 0) {
|
||||||
result = this->parent_->button_ext_heat();
|
result = this->parent_->button_ext_heat();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str());
|
ESP_LOGW(TAG, "Unsupported preset: %s", preset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->custom_preset = preset;
|
this->set_custom_preset_(preset);
|
||||||
this->preset.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,19 +205,16 @@ void BedJetClimate::control(const ClimateCall &call) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this->fan_mode = fan_mode;
|
this->set_fan_mode_(fan_mode);
|
||||||
this->custom_fan_mode.reset();
|
|
||||||
}
|
}
|
||||||
} else if (call.get_custom_fan_mode().has_value()) {
|
} else if (call.has_custom_fan_mode()) {
|
||||||
auto fan_mode = *call.get_custom_fan_mode();
|
const char *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.c_str(),
|
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index);
|
||||||
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->custom_fan_mode = fan_mode;
|
this->set_custom_fan_mode_(fan_mode);
|
||||||
this->fan_mode.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +240,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->custom_fan_mode = *fan_mode_name;
|
this->set_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.
|
||||||
@@ -255,7 +250,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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -266,7 +261,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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -275,7 +270,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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
} else {
|
} else {
|
||||||
this->set_custom_preset_("EXT HT");
|
this->set_custom_preset_("EXT HT");
|
||||||
}
|
}
|
||||||
@@ -284,20 +279,19 @@ 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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
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->custom_preset.reset();
|
this->clear_custom_preset_();
|
||||||
this->preset.reset();
|
this->preset.reset();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MODE_TURBO:
|
case MODE_TURBO:
|
||||||
this->preset = CLIMATE_PRESET_BOOST;
|
this->set_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;
|
||||||
|
|||||||
@@ -50,21 +50,13 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
|
|||||||
// 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({
|
||||||
// We could fetch biodata from bedjet and set these names that way.
|
this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT",
|
||||||
// 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);
|
||||||
|
|||||||
@@ -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(Ts... x) override { return this->parent_->state == this->state_; }
|
bool check(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override { this->sensor_->invalidate_state(); }
|
void play(const Ts &...x) override { this->sensor_->invalidate_state(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BinarySensor *sensor_;
|
BinarySensor *sensor_;
|
||||||
|
|||||||
@@ -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(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
void play(const Ts &...x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bl0906
|
} // namespace bl0906
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
|||||||
this->has_simple_value_ = true;
|
this->has_simple_value_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(Ts... x) override {}
|
void play(const Ts &...x) override {}
|
||||||
|
|
||||||
void play_complex(Ts... x) override {
|
void play_complex(const Ts &...x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
this->var_ = std::make_tuple(x...);
|
this->var_ = std::make_tuple(x...);
|
||||||
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
|
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
|
||||||
@@ -229,7 +229,7 @@ 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(Ts... x) override {
|
void play(const 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;
|
||||||
@@ -266,7 +266,7 @@ template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Ac
|
|||||||
public:
|
public:
|
||||||
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const 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_) {
|
||||||
@@ -299,7 +299,7 @@ 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(Ts... x) override {
|
void play(const 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);
|
||||||
@@ -334,9 +334,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(Ts... x) override {}
|
void play(const Ts &...x) override {}
|
||||||
|
|
||||||
void play_complex(Ts... x) override {
|
void play_complex(const 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.
|
||||||
@@ -379,9 +379,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(Ts... x) override {}
|
void play(const Ts &...x) override {}
|
||||||
|
|
||||||
void play_complex(Ts... x) override {
|
void play_complex(const 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,6 +77,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(Ts... x) override { this->button_->press(); }
|
void play(const Ts &...x) override { this->button_->press(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Button *button_;
|
Button *button_;
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ 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(Ts... x) override {
|
void play(const 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_;
|
||||||
|
|||||||
@@ -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(Ts... x) override {
|
void play(const 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...));
|
||||||
|
|||||||
@@ -50,21 +50,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_.has_value()) {
|
if (this->custom_fan_mode_ != nullptr) {
|
||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str());
|
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_);
|
||||||
}
|
}
|
||||||
if (this->fan_mode_.has_value()) {
|
if (this->fan_mode_.has_value()) {
|
||||||
this->custom_fan_mode_.reset();
|
this->custom_fan_mode_ = nullptr;
|
||||||
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_.has_value()) {
|
if (this->custom_preset_ != nullptr) {
|
||||||
this->preset_.reset();
|
this->preset_.reset();
|
||||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str());
|
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||||
}
|
}
|
||||||
if (this->preset_.has_value()) {
|
if (this->preset_.has_value()) {
|
||||||
this->custom_preset_.reset();
|
this->custom_preset_ = nullptr;
|
||||||
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));
|
||||||
}
|
}
|
||||||
@@ -96,11 +96,10 @@ void ClimateCall::validate_() {
|
|||||||
this->mode_.reset();
|
this->mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->custom_fan_mode_.has_value()) {
|
if (this->custom_fan_mode_ != nullptr) {
|
||||||
auto custom_fan_mode = *this->custom_fan_mode_;
|
if (!traits.supports_custom_fan_mode(this->custom_fan_mode_)) {
|
||||||
if (!traits.supports_custom_fan_mode(custom_fan_mode)) {
|
ESP_LOGW(TAG, " Fan Mode %s not supported", this->custom_fan_mode_);
|
||||||
ESP_LOGW(TAG, " Fan Mode %s not supported", custom_fan_mode.c_str());
|
this->custom_fan_mode_ = nullptr;
|
||||||
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_;
|
||||||
@@ -109,11 +108,10 @@ void ClimateCall::validate_() {
|
|||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->custom_preset_.has_value()) {
|
if (this->custom_preset_ != nullptr) {
|
||||||
auto custom_preset = *this->custom_preset_;
|
if (!traits.supports_custom_preset(this->custom_preset_)) {
|
||||||
if (!traits.supports_custom_preset(custom_preset)) {
|
ESP_LOGW(TAG, " Preset %s not supported", this->custom_preset_);
|
||||||
ESP_LOGW(TAG, " Preset %s not supported", custom_preset.c_str());
|
this->custom_preset_ = nullptr;
|
||||||
this->custom_preset_.reset();
|
|
||||||
}
|
}
|
||||||
} else if (this->preset_.has_value()) {
|
} else if (this->preset_.has_value()) {
|
||||||
auto preset = *this->preset_;
|
auto preset = *this->preset_;
|
||||||
@@ -186,26 +184,29 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
|||||||
|
|
||||||
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_.reset();
|
this->custom_fan_mode_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
|
||||||
|
// Check if it's a standard enum mode first
|
||||||
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
||||||
if (str_equals_case_insensitive(fan_mode, mode_entry.str)) {
|
if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) {
|
||||||
this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
// Find the matching pointer from parent climate device
|
||||||
this->custom_fan_mode_ = fan_mode;
|
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) {
|
||||||
|
this->custom_fan_mode_ = mode_ptr;
|
||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
} else {
|
return *this;
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
|
|
||||||
}
|
}
|
||||||
|
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());
|
||||||
@@ -215,26 +216,29 @@ ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
|||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
||||||
this->preset_ = preset;
|
this->preset_ = preset;
|
||||||
this->custom_preset_.reset();
|
this->custom_preset_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
|
||||||
|
// Check if it's a standard enum preset first
|
||||||
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
||||||
if (str_equals_case_insensitive(preset, preset_entry.str)) {
|
if (str_equals_case_insensitive(custom_preset, preset_entry.str)) {
|
||||||
this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->parent_->get_traits().supports_custom_preset(preset)) {
|
// Find the matching pointer from parent climate device
|
||||||
this->custom_preset_ = preset;
|
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) {
|
||||||
|
this->custom_preset_ = preset_ptr;
|
||||||
this->preset_.reset();
|
this->preset_.reset();
|
||||||
} else {
|
return *this;
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str());
|
|
||||||
}
|
}
|
||||||
|
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());
|
||||||
@@ -287,8 +291,6 @@ 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<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_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_fan_mode() const { return this->custom_fan_mode_; }
|
|
||||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -317,13 +319,13 @@ ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
|||||||
|
|
||||||
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_.reset();
|
this->custom_fan_mode_ = nullptr;
|
||||||
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_.reset();
|
this->custom_preset_ = nullptr;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,13 +384,13 @@ 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() && custom_fan_mode.has_value()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
|
||||||
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 maintains insertion order
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (const auto &mode : supported) {
|
for (const char *mode : supported) {
|
||||||
if (mode == custom_fan_mode) {
|
if (strcmp(mode, this->custom_fan_mode_) == 0) {
|
||||||
state.custom_fan_mode = i;
|
state.custom_fan_mode = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -399,13 +401,13 @@ void Climate::save_state_() {
|
|||||||
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() && custom_preset.has_value()) {
|
if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
|
||||||
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 maintains insertion order
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (const auto &preset : supported) {
|
for (const char *preset : supported) {
|
||||||
if (preset == custom_preset) {
|
if (strcmp(preset, this->custom_preset_) == 0) {
|
||||||
state.custom_preset = i;
|
state.custom_preset = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -430,14 +432,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->custom_fan_mode.has_value()) {
|
if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
|
||||||
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str());
|
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_);
|
||||||
}
|
}
|
||||||
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->custom_preset.has_value()) {
|
if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
|
||||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str());
|
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||||
}
|
}
|
||||||
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)));
|
||||||
@@ -527,7 +529,7 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||||||
if (this->uses_custom_fan_mode) {
|
if (this->uses_custom_fan_mode) {
|
||||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
||||||
call.fan_mode_.reset();
|
call.fan_mode_.reset();
|
||||||
call.custom_fan_mode_ = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
|
call.custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
||||||
}
|
}
|
||||||
} else if (traits.supports_fan_mode(this->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);
|
||||||
@@ -535,7 +537,7 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
|||||||
if (this->uses_custom_preset) {
|
if (this->uses_custom_preset) {
|
||||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
||||||
call.preset_.reset();
|
call.preset_.reset();
|
||||||
call.custom_preset_ = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
|
call.custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
|
||||||
}
|
}
|
||||||
} else if (traits.supports_preset(this->preset)) {
|
} else if (traits.supports_preset(this->preset)) {
|
||||||
call.set_preset(this->preset);
|
call.set_preset(this->preset);
|
||||||
@@ -562,20 +564,20 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
|||||||
if (this->uses_custom_fan_mode) {
|
if (this->uses_custom_fan_mode) {
|
||||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
||||||
climate->fan_mode.reset();
|
climate->fan_mode.reset();
|
||||||
climate->custom_fan_mode = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
|
climate->custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
||||||
}
|
}
|
||||||
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
||||||
climate->fan_mode = this->fan_mode;
|
climate->fan_mode = this->fan_mode;
|
||||||
climate->custom_fan_mode.reset();
|
climate->clear_custom_fan_mode_();
|
||||||
}
|
}
|
||||||
if (this->uses_custom_preset) {
|
if (this->uses_custom_preset) {
|
||||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
||||||
climate->preset.reset();
|
climate->preset.reset();
|
||||||
climate->custom_preset = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
|
climate->custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
|
||||||
}
|
}
|
||||||
} else if (traits.supports_preset(this->preset)) {
|
} else if (traits.supports_preset(this->preset)) {
|
||||||
climate->preset = this->preset;
|
climate->preset = this->preset;
|
||||||
climate->custom_preset.reset();
|
climate->clear_custom_preset_();
|
||||||
}
|
}
|
||||||
if (traits.supports_swing_mode(this->swing_mode)) {
|
if (traits.supports_swing_mode(this->swing_mode)) {
|
||||||
climate->swing_mode = this->swing_mode;
|
climate->swing_mode = this->swing_mode;
|
||||||
@@ -583,28 +585,107 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
|||||||
climate->publish_state();
|
climate->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T1, typename T2> bool set_alternative(optional<T1> &dst, optional<T2> &alt, const T1 &src) {
|
/** Template helper for setting primary modes (fan_mode, preset) with mutual exclusion.
|
||||||
bool is_changed = alt.has_value();
|
*
|
||||||
alt.reset();
|
* Climate devices have mutually exclusive mode pairs:
|
||||||
if (is_changed || dst != src) {
|
* - fan_mode (enum) vs custom_fan_mode_ (const char*)
|
||||||
dst = src;
|
* - preset (enum) vs custom_preset_ (const char*)
|
||||||
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 is_changed;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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_alternative(this->fan_mode, this->custom_fan_mode, mode);
|
return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Climate::set_custom_fan_mode_(const std::string &mode) {
|
bool Climate::set_custom_fan_mode_(const char *mode) {
|
||||||
return set_alternative(this->custom_fan_mode, this->fan_mode, mode);
|
auto traits = this->get_traits();
|
||||||
|
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode),
|
||||||
|
this->has_custom_fan_mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); }
|
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
||||||
|
|
||||||
bool Climate::set_custom_preset_(const std::string &preset) {
|
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, 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) {
|
||||||
@@ -656,8 +737,8 @@ void Climate::dump_traits_(const char *tag) {
|
|||||||
}
|
}
|
||||||
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, " Supported custom fan modes:");
|
||||||
for (const std::string &s : traits.get_supported_custom_fan_modes())
|
for (const char *s : traits.get_supported_custom_fan_modes())
|
||||||
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
ESP_LOGCONFIG(tag, " - %s", s);
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_presets().empty()) {
|
if (!traits.get_supported_presets().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported presets:");
|
ESP_LOGCONFIG(tag, " Supported presets:");
|
||||||
@@ -666,8 +747,8 @@ void Climate::dump_traits_(const char *tag) {
|
|||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty()) {
|
if (!traits.get_supported_custom_presets().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported custom presets:");
|
ESP_LOGCONFIG(tag, " Supported custom presets:");
|
||||||
for (const std::string &s : traits.get_supported_custom_presets())
|
for (const char *s : traits.get_supported_custom_presets())
|
||||||
ESP_LOGCONFIG(tag, " - %s", s.c_str());
|
ESP_LOGCONFIG(tag, " - %s", s);
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_swing_modes().empty()) {
|
if (!traits.get_supported_swing_modes().empty()) {
|
||||||
ESP_LOGCONFIG(tag, " Supported swing modes:");
|
ESP_LOGCONFIG(tag, " Supported swing modes:");
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ 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.
|
||||||
@@ -91,6 +93,8 @@ 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();
|
||||||
|
|
||||||
@@ -103,8 +107,10 @@ class ClimateCall {
|
|||||||
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<ClimatePreset> &get_preset() const;
|
const optional<ClimatePreset> &get_preset() const;
|
||||||
const optional<std::string> &get_custom_fan_mode() 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_();
|
||||||
@@ -118,8 +124,10 @@ class ClimateCall {
|
|||||||
optional<ClimateFanMode> fan_mode_;
|
optional<ClimateFanMode> fan_mode_;
|
||||||
optional<ClimateSwingMode> swing_mode_;
|
optional<ClimateSwingMode> swing_mode_;
|
||||||
optional<ClimatePreset> preset_;
|
optional<ClimatePreset> preset_;
|
||||||
optional<std::string> custom_fan_mode_;
|
|
||||||
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.
|
||||||
@@ -212,6 +220,12 @@ 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.
|
/// The current temperature of the climate device, as reported from the integration.
|
||||||
float current_temperature{NAN};
|
float current_temperature{NAN};
|
||||||
|
|
||||||
@@ -238,12 +252,6 @@ class Climate : public EntityBase {
|
|||||||
/// The active preset of the climate device.
|
/// The active preset of the climate device.
|
||||||
optional<ClimatePreset> preset;
|
optional<ClimatePreset> preset;
|
||||||
|
|
||||||
/// The active custom fan mode of the climate device.
|
|
||||||
optional<std::string> custom_fan_mode;
|
|
||||||
|
|
||||||
/// The active custom preset mode of the climate device.
|
|
||||||
optional<std::string> custom_preset;
|
|
||||||
|
|
||||||
/// The active mode of the climate device.
|
/// The active mode of the climate device.
|
||||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||||
|
|
||||||
@@ -253,20 +261,37 @@ class Climate : public EntityBase {
|
|||||||
/// The active swing mode of the climate device.
|
/// The active swing mode of the climate device.
|
||||||
ClimateSwingMode swing_mode{CLIMATE_SWING_OFF};
|
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 std::string &mode);
|
bool set_custom_fan_mode_(const char *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 std::string &preset);
|
bool set_custom_preset_(const char *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.
|
||||||
*
|
*
|
||||||
@@ -303,6 +328,21 @@ 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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "climate_mode.h"
|
#include "climate_mode.h"
|
||||||
#include "esphome/core/finite_set_mask.h"
|
#include "esphome/core/finite_set_mask.h"
|
||||||
@@ -18,16 +19,25 @@ using ClimateSwingModeMask =
|
|||||||
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
|
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
|
||||||
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
|
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
|
||||||
|
|
||||||
// Lightweight linear search for small vectors (1-20 items)
|
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
||||||
// Avoids std::find template overhead
|
// Avoids std::find template overhead
|
||||||
template<typename T> inline bool vector_contains(const std::vector<T> &vec, const T &value) {
|
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
||||||
for (const auto &item : vec) {
|
for (const char *item : vec) {
|
||||||
if (item == value)
|
if (strcmp(item, value) == 0)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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:
|
||||||
@@ -55,7 +65,11 @@ template<typename T> inline bool vector_contains(const std::vector<T> &vec, cons
|
|||||||
* - 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_; }
|
||||||
@@ -128,47 +142,61 @@ class ClimateTraits {
|
|||||||
|
|
||||||
void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; }
|
void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = 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_.push_back(mode); }
|
|
||||||
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 ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||||
|
|
||||||
void set_supported_custom_fan_modes(std::vector<std::string> supported_custom_fan_modes) {
|
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||||
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
this->supported_custom_fan_modes_ = modes;
|
||||||
}
|
}
|
||||||
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) {
|
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
||||||
this->supported_custom_fan_modes_ = modes;
|
this->supported_custom_fan_modes_ = modes;
|
||||||
}
|
}
|
||||||
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
||||||
this->supported_custom_fan_modes_.assign(modes, modes + N);
|
this->supported_custom_fan_modes_.assign(modes, modes + N);
|
||||||
}
|
}
|
||||||
const std::vector<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 {
|
// 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);
|
return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
|
||||||
}
|
}
|
||||||
|
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||||
|
return this->supports_custom_fan_mode(custom_fan_mode.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
|
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = 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_.push_back(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 ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
|
||||||
|
|
||||||
void set_supported_custom_presets(std::vector<std::string> supported_custom_presets) {
|
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
||||||
this->supported_custom_presets_ = std::move(supported_custom_presets);
|
this->supported_custom_presets_ = presets;
|
||||||
}
|
}
|
||||||
void set_supported_custom_presets(std::initializer_list<std::string> presets) {
|
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
||||||
this->supported_custom_presets_ = presets;
|
this->supported_custom_presets_ = presets;
|
||||||
}
|
}
|
||||||
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
||||||
this->supported_custom_presets_.assign(presets, presets + N);
|
this->supported_custom_presets_.assign(presets, presets + N);
|
||||||
}
|
}
|
||||||
const std::vector<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
|
||||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
// 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);
|
return vector_contains(this->supported_custom_presets_, custom_preset);
|
||||||
}
|
}
|
||||||
|
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||||
|
return this->supports_custom_preset(custom_preset.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
|
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = 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); }
|
||||||
@@ -227,6 +255,18 @@ 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};
|
||||||
@@ -239,8 +279,17 @@ class ClimateTraits {
|
|||||||
climate::ClimateFanModeMask supported_fan_modes_;
|
climate::ClimateFanModeMask supported_fan_modes_;
|
||||||
climate::ClimateSwingModeMask supported_swing_modes_;
|
climate::ClimateSwingModeMask supported_swing_modes_;
|
||||||
climate::ClimatePresetMask supported_presets_;
|
climate::ClimatePresetMask supported_presets_;
|
||||||
std::vector<std::string> supported_custom_fan_modes_;
|
|
||||||
std::vector<std::string> supported_custom_presets_;
|
/** Custom mode storage using const char* pointers to eliminate std::string overhead.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|||||||
@@ -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(Ts... x) override { this->cm1106_->calibrate_zero(400); }
|
void play(const Ts &...x) override { this->cm1106_->calibrate_zero(400); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CM1106Component *cm1106_;
|
CM1106Component *cm1106_;
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ 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_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->preset_mode = source_->preset_mode;
|
this->set_preset_mode_(source_->get_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->preset_mode = source_->preset_mode;
|
this->set_preset_mode_(source_->get_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.get_preset_mode().empty())
|
if (call.has_preset_mode())
|
||||||
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(value); });
|
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); });
|
||||||
|
|
||||||
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_->state);
|
this->publish_state(source_->active_index().value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
||||||
|
|
||||||
void CopySelect::control(const std::string &value) {
|
void CopySelect::control(size_t index) {
|
||||||
auto call = source_->make_call();
|
auto call = source_->make_call();
|
||||||
call.set_option(value);
|
call.set_index(index);
|
||||||
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(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
|
|
||||||
select::Select *source_;
|
select::Select *source_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(Ts... x) override { this->cover_->make_call().set_command_open().perform(); }
|
void play(const 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(Ts... x) override { this->cover_->make_call().set_command_close().perform(); }
|
void play(const 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(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); }
|
void play(const 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(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); }
|
void play(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override {
|
void play(const 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(Ts... x) override { return this->cover_->is_fully_open(); }
|
bool check(const 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(Ts... x) override { return this->cover_->is_fully_closed(); }
|
bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
|
|||||||
@@ -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(Ts... x) override { cs5460a_->restart(); }
|
void play(const Ts &...x) override { cs5460a_->restart(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CS5460AComponent *cs5460a_;
|
CS5460AComponent *cs5460a_;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DallasTemperatureSensor::setup() {
|
void DallasTemperatureSensor::setup() {
|
||||||
if (!this->check_address_())
|
if (!this->check_address_or_index_())
|
||||||
return;
|
return;
|
||||||
if (!this->read_scratch_pad_())
|
if (!this->read_scratch_pad_())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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(Ts... x) override {
|
void play(const 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()) {
|
||||||
|
|||||||
@@ -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(Ts... x) override {
|
void play(const 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()) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Pare
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(ESPTime, time)
|
TEMPLATABLE_VALUE(ESPTime, time)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
|
|
||||||
if (this->time_.has_value()) {
|
if (this->time_.has_value()) {
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::debug {
|
||||||
namespace debug {
|
|
||||||
|
|
||||||
static const char *const TAG = "debug";
|
static const char *const TAG = "debug";
|
||||||
constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC;
|
constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC;
|
||||||
@@ -281,14 +280,18 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
|||||||
NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE));
|
NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE));
|
||||||
ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH,
|
ESP_LOGD(TAG, "RAM: %ukB, Flash: %ukB, production test: %sdone", NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH,
|
||||||
(NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
(NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
||||||
|
bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] &&
|
||||||
|
(NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected
|
||||||
|
<< UICR_PSELRESET_CONNECT_Pos;
|
||||||
ESP_LOGD(
|
ESP_LOGD(
|
||||||
TAG, "GPIO as NFC pins: %s, GPIO as nRESET pin: %s",
|
TAG, "GPIO as NFC pins: %s, GPIO as nRESET pin: %s",
|
||||||
YESNO((NRF_UICR->NFCPINS & UICR_NFCPINS_PROTECT_Msk) == (UICR_NFCPINS_PROTECT_NFC << UICR_NFCPINS_PROTECT_Pos)),
|
YESNO((NRF_UICR->NFCPINS & UICR_NFCPINS_PROTECT_Msk) == (UICR_NFCPINS_PROTECT_NFC << UICR_NFCPINS_PROTECT_Pos)),
|
||||||
YESNO(((NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) !=
|
YESNO(n_reset_enabled));
|
||||||
(UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos)) ||
|
if (n_reset_enabled) {
|
||||||
((NRF_UICR->PSELRESET[1] & UICR_PSELRESET_CONNECT_Msk) !=
|
uint8_t port = (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_PORT_Msk) >> UICR_PSELRESET_PORT_Pos;
|
||||||
(UICR_PSELRESET_CONNECT_Connected << UICR_PSELRESET_CONNECT_Pos))));
|
uint8_t pin = (NRF_UICR->PSELRESET[0] & UICR_PSELRESET_PIN_Msk) >> UICR_PSELRESET_PIN_Pos;
|
||||||
|
ESP_LOGD(TAG, "nRESET port P%u.%02u", port, pin);
|
||||||
|
}
|
||||||
#ifdef USE_BOOTLOADER_MCUBOOT
|
#ifdef USE_BOOTLOADER_MCUBOOT
|
||||||
ESP_LOGD(TAG, "bootloader: mcuboot");
|
ESP_LOGD(TAG, "bootloader: mcuboot");
|
||||||
#else
|
#else
|
||||||
@@ -322,10 +325,22 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
auto uicr = [](volatile uint32_t *data, uint8_t size) {
|
||||||
|
std::string res;
|
||||||
|
char buf[sizeof(uint32_t) * 2 + 1];
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
res += ' ';
|
||||||
|
}
|
||||||
|
res += format_hex_pretty<uint32_t>(data[i], '\0', false);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
ESP_LOGD(TAG, "NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str());
|
||||||
|
ESP_LOGD(TAG, "NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugComponent::update_platform_() {}
|
void DebugComponent::update_platform_() {}
|
||||||
|
|
||||||
} // namespace debug
|
} // namespace esphome::debug
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
|
|||||||
void set_time(time::RealTimeClock *time) { this->time_ = time; }
|
void set_time(time::RealTimeClock *time) { this->time_ = time; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
if (this->sleep_duration_.has_value()) {
|
if (this->sleep_duration_.has_value()) {
|
||||||
this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
|
this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
|
||||||
}
|
}
|
||||||
@@ -207,12 +207,12 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
|
|||||||
|
|
||||||
template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
|
template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { this->parent_->prevent_deep_sleep(); }
|
void play(const Ts &...x) override { this->parent_->prevent_deep_sleep(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class AllowDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
|
template<typename... Ts> class AllowDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { this->parent_->allow_deep_sleep(); }
|
void play(const Ts &...x) override { this->parent_->allow_deep_sleep(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace deep_sleep
|
} // namespace deep_sleep
|
||||||
|
|||||||
@@ -28,16 +28,16 @@ class DemoClimate : public climate::Climate, public Component {
|
|||||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||||
this->action = climate::CLIMATE_ACTION_COOLING;
|
this->action = climate::CLIMATE_ACTION_COOLING;
|
||||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
this->custom_preset = {"My Preset"};
|
this->set_custom_preset_("My Preset");
|
||||||
break;
|
break;
|
||||||
case DemoClimateType::TYPE_3:
|
case DemoClimateType::TYPE_3:
|
||||||
this->current_temperature = 21.5;
|
this->current_temperature = 21.5;
|
||||||
this->target_temperature_low = 21.0;
|
this->target_temperature_low = 21.0;
|
||||||
this->target_temperature_high = 22.5;
|
this->target_temperature_high = 22.5;
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
this->custom_fan_mode = {"Auto Low"};
|
this->set_custom_fan_mode_("Auto Low");
|
||||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||||
this->preset = climate::CLIMATE_PRESET_AWAY;
|
this->set_preset_(climate::CLIMATE_PRESET_AWAY);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
@@ -58,23 +58,19 @@ class DemoClimate : public climate::Climate, public Component {
|
|||||||
this->target_temperature_high = *call.get_target_temperature_high();
|
this->target_temperature_high = *call.get_target_temperature_high();
|
||||||
}
|
}
|
||||||
if (call.get_fan_mode().has_value()) {
|
if (call.get_fan_mode().has_value()) {
|
||||||
this->fan_mode = *call.get_fan_mode();
|
this->set_fan_mode_(*call.get_fan_mode());
|
||||||
this->custom_fan_mode.reset();
|
|
||||||
}
|
}
|
||||||
if (call.get_swing_mode().has_value()) {
|
if (call.get_swing_mode().has_value()) {
|
||||||
this->swing_mode = *call.get_swing_mode();
|
this->swing_mode = *call.get_swing_mode();
|
||||||
}
|
}
|
||||||
if (call.get_custom_fan_mode().has_value()) {
|
if (call.has_custom_fan_mode()) {
|
||||||
this->custom_fan_mode = *call.get_custom_fan_mode();
|
this->set_custom_fan_mode_(call.get_custom_fan_mode());
|
||||||
this->fan_mode.reset();
|
|
||||||
}
|
}
|
||||||
if (call.get_preset().has_value()) {
|
if (call.get_preset().has_value()) {
|
||||||
this->preset = *call.get_preset();
|
this->set_preset_(*call.get_preset());
|
||||||
this->custom_preset.reset();
|
|
||||||
}
|
}
|
||||||
if (call.get_custom_preset().has_value()) {
|
if (call.has_custom_preset()) {
|
||||||
this->custom_preset = *call.get_custom_preset();
|
this->set_custom_preset_(call.get_custom_preset());
|
||||||
this->preset.reset();
|
|
||||||
}
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace demo {
|
|||||||
|
|
||||||
class DemoSelect : public select::Select, public Component {
|
class DemoSelect : public select::Select, public Component {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override { this->publish_state(value); }
|
void control(size_t index) override { this->publish_state(index); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace demo
|
} // namespace demo
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
|||||||
class ACTION_CLASS : /* NOLINT */ \
|
class ACTION_CLASS : /* NOLINT */ \
|
||||||
public Action<Ts...>, \
|
public Action<Ts...>, \
|
||||||
public Parented<DFPlayer> { \
|
public Parented<DFPlayer> { \
|
||||||
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
|
void play(const Ts &...x) override { this->parent_->ACTION_METHOD(); } \
|
||||||
};
|
};
|
||||||
|
|
||||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||||
@@ -87,7 +87,7 @@ template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Pare
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint16_t, file)
|
TEMPLATABLE_VALUE(uint16_t, file)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto file = this->file_.value(x...);
|
auto file = this->file_.value(x...);
|
||||||
this->parent_->play_mp3(file);
|
this->parent_->play_mp3(file);
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Par
|
|||||||
TEMPLATABLE_VALUE(uint16_t, file)
|
TEMPLATABLE_VALUE(uint16_t, file)
|
||||||
TEMPLATABLE_VALUE(bool, loop)
|
TEMPLATABLE_VALUE(bool, loop)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto file = this->file_.value(x...);
|
auto file = this->file_.value(x...);
|
||||||
auto loop = this->loop_.value(x...);
|
auto loop = this->loop_.value(x...);
|
||||||
if (loop) {
|
if (loop) {
|
||||||
@@ -115,7 +115,7 @@ template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public P
|
|||||||
TEMPLATABLE_VALUE(uint16_t, file)
|
TEMPLATABLE_VALUE(uint16_t, file)
|
||||||
TEMPLATABLE_VALUE(bool, loop)
|
TEMPLATABLE_VALUE(bool, loop)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto folder = this->folder_.value(x...);
|
auto folder = this->folder_.value(x...);
|
||||||
auto file = this->file_.value(x...);
|
auto file = this->file_.value(x...);
|
||||||
auto loop = this->loop_.value(x...);
|
auto loop = this->loop_.value(x...);
|
||||||
@@ -131,7 +131,7 @@ template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Pa
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(Device, device)
|
TEMPLATABLE_VALUE(Device, device)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto device = this->device_.value(x...);
|
auto device = this->device_.value(x...);
|
||||||
this->parent_->set_device(device);
|
this->parent_->set_device(device);
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Pa
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto volume = this->volume_.value(x...);
|
auto volume = this->volume_.value(x...);
|
||||||
this->parent_->set_volume(volume);
|
this->parent_->set_volume(volume);
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parent
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto eq = this->eq_.value(x...);
|
auto eq = this->eq_.value(x...);
|
||||||
this->parent_->set_eq(eq);
|
this->parent_->set_eq(eq);
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ DFPLAYER_SIMPLE_ACTION(VolumeDownAction, volume_down)
|
|||||||
|
|
||||||
template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts...>, public Parented<DFPlayer> {
|
template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts...>, public Parented<DFPlayer> {
|
||||||
public:
|
public:
|
||||||
bool check(Ts... x) override { return this->parent_->is_playing(); }
|
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class DFPlayerFinishedPlaybackTrigger : public Trigger<> {
|
class DFPlayerFinishedPlaybackTrigger : public Trigger<> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace dfrobot_sen0395 {
|
|||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
class DfrobotSen0395ResetAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
|
class DfrobotSen0395ResetAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) { this->parent_->enqueue(make_unique<ResetSystemCommand>()); }
|
void play(const Ts &...x) { this->parent_->enqueue(make_unique<ResetSystemCommand>()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
@@ -33,7 +33,7 @@ class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<Dfrob
|
|||||||
TEMPLATABLE_VALUE(float, det_min4)
|
TEMPLATABLE_VALUE(float, det_min4)
|
||||||
TEMPLATABLE_VALUE(float, det_max4)
|
TEMPLATABLE_VALUE(float, det_max4)
|
||||||
|
|
||||||
void play(Ts... x) {
|
void play(const Ts &...x) {
|
||||||
this->parent_->enqueue(make_unique<PowerCommand>(0));
|
this->parent_->enqueue(make_unique<PowerCommand>(0));
|
||||||
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) {
|
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) {
|
||||||
this->parent_->enqueue(make_unique<FactoryResetCommand>());
|
this->parent_->enqueue(make_unique<FactoryResetCommand>());
|
||||||
|
|||||||
@@ -176,7 +176,117 @@ class Display;
|
|||||||
class DisplayPage;
|
class DisplayPage;
|
||||||
class DisplayOnPageChangeTrigger;
|
class DisplayOnPageChangeTrigger;
|
||||||
|
|
||||||
using display_writer_t = std::function<void(Display &)>;
|
/** Optimized display writer that uses function pointers for stateless lambdas.
|
||||||
|
*
|
||||||
|
* Similar to TemplatableValue but specialized for display writer callbacks.
|
||||||
|
* Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer).
|
||||||
|
*
|
||||||
|
* Supports both:
|
||||||
|
* - Stateless lambdas (from YAML) → function pointer (4 bytes)
|
||||||
|
* - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated)
|
||||||
|
*
|
||||||
|
* @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay)
|
||||||
|
*/
|
||||||
|
template<typename T> class DisplayWriter {
|
||||||
|
public:
|
||||||
|
DisplayWriter() : type_(NONE) {}
|
||||||
|
|
||||||
|
// For stateless lambdas (convertible to function pointer): use function pointer (4 bytes)
|
||||||
|
template<typename F>
|
||||||
|
DisplayWriter(F f) requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
|
||||||
|
: type_(STATELESS_LAMBDA) {
|
||||||
|
this->stateless_f_ = f; // Implicit conversion to function pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated)
|
||||||
|
// This handles backwards compatibility with external components
|
||||||
|
template<typename F>
|
||||||
|
DisplayWriter(F f) requires std::invocable<F, T &> &&(!std::convertible_to<F, void (*)(T &)>) : type_(LAMBDA) {
|
||||||
|
this->f_ = new std::function<void(T &)>(std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
DisplayWriter(const DisplayWriter &other) : type_(other.type_) {
|
||||||
|
if (type_ == LAMBDA) {
|
||||||
|
this->f_ = new std::function<void(T &)>(*other.f_);
|
||||||
|
} else if (type_ == STATELESS_LAMBDA) {
|
||||||
|
this->stateless_f_ = other.stateless_f_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move constructor
|
||||||
|
DisplayWriter(DisplayWriter &&other) noexcept : type_(other.type_) {
|
||||||
|
if (type_ == LAMBDA) {
|
||||||
|
this->f_ = other.f_;
|
||||||
|
other.f_ = nullptr;
|
||||||
|
} else if (type_ == STATELESS_LAMBDA) {
|
||||||
|
this->stateless_f_ = other.stateless_f_;
|
||||||
|
}
|
||||||
|
other.type_ = NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assignment operators
|
||||||
|
DisplayWriter &operator=(const DisplayWriter &other) {
|
||||||
|
if (this != &other) {
|
||||||
|
this->~DisplayWriter();
|
||||||
|
new (this) DisplayWriter(other);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayWriter &operator=(DisplayWriter &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
this->~DisplayWriter();
|
||||||
|
new (this) DisplayWriter(std::move(other));
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~DisplayWriter() {
|
||||||
|
if (type_ == LAMBDA) {
|
||||||
|
delete this->f_;
|
||||||
|
}
|
||||||
|
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_value() const { return this->type_ != NONE; }
|
||||||
|
|
||||||
|
void call(T &display) const {
|
||||||
|
switch (this->type_) {
|
||||||
|
case STATELESS_LAMBDA:
|
||||||
|
this->stateless_f_(display); // Direct function pointer call
|
||||||
|
break;
|
||||||
|
case LAMBDA:
|
||||||
|
(*this->f_)(display); // std::function call
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operator() for convenience
|
||||||
|
void operator()(T &display) const { this->call(display); }
|
||||||
|
|
||||||
|
// Operator* for backwards compatibility with (*writer_)(*this) pattern
|
||||||
|
DisplayWriter &operator*() { return *this; }
|
||||||
|
const DisplayWriter &operator*() const { return *this; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum : uint8_t {
|
||||||
|
NONE,
|
||||||
|
LAMBDA,
|
||||||
|
STATELESS_LAMBDA,
|
||||||
|
} type_;
|
||||||
|
|
||||||
|
union {
|
||||||
|
std::function<void(T &)> *f_;
|
||||||
|
void (*stateless_f_)(T &);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type alias for Display writer - uses optimized DisplayWriter instead of std::function
|
||||||
|
using display_writer_t = DisplayWriter<Display>;
|
||||||
|
|
||||||
#define LOG_DISPLAY(prefix, type, obj) \
|
#define LOG_DISPLAY(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
@@ -210,7 +320,7 @@ class Display : public PollingComponent {
|
|||||||
/// Fill the entire screen with the given color.
|
/// Fill the entire screen with the given color.
|
||||||
virtual void fill(Color color);
|
virtual void fill(Color color);
|
||||||
/// Clear the entire screen by filling it with OFF pixels.
|
/// Clear the entire screen by filling it with OFF pixels.
|
||||||
void clear();
|
virtual void clear();
|
||||||
|
|
||||||
/// Get the calculated width of the display in pixels with rotation applied.
|
/// Get the calculated width of the display in pixels with rotation applied.
|
||||||
virtual int get_width() { return this->get_width_internal(); }
|
virtual int get_width() { return this->get_width_internal(); }
|
||||||
@@ -678,7 +788,7 @@ class Display : public PollingComponent {
|
|||||||
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
|
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
|
||||||
|
|
||||||
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
|
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
|
||||||
optional<display_writer_t> writer_{};
|
display_writer_t writer_{};
|
||||||
DisplayPage *page_{nullptr};
|
DisplayPage *page_{nullptr};
|
||||||
DisplayPage *previous_page_{nullptr};
|
DisplayPage *previous_page_{nullptr};
|
||||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||||
@@ -709,7 +819,7 @@ template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto *page = this->page_.value(x...);
|
auto *page = this->page_.value(x...);
|
||||||
if (page != nullptr) {
|
if (page != nullptr) {
|
||||||
page->show();
|
page->show();
|
||||||
@@ -721,7 +831,7 @@ template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...>
|
|||||||
public:
|
public:
|
||||||
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
|
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->buffer_->show_next_page(); }
|
void play(const Ts &...x) override { this->buffer_->show_next_page(); }
|
||||||
|
|
||||||
Display *buffer_;
|
Display *buffer_;
|
||||||
};
|
};
|
||||||
@@ -730,7 +840,7 @@ template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...>
|
|||||||
public:
|
public:
|
||||||
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
|
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->buffer_->show_prev_page(); }
|
void play(const Ts &...x) override { this->buffer_->show_prev_page(); }
|
||||||
|
|
||||||
Display *buffer_;
|
Display *buffer_;
|
||||||
};
|
};
|
||||||
@@ -740,7 +850,7 @@ template<typename... Ts> class DisplayIsDisplayingPageCondition : public Conditi
|
|||||||
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
|
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
|
||||||
|
|
||||||
void set_page(DisplayPage *page) { this->page_ = page; }
|
void set_page(DisplayPage *page) { this->page_ = page; }
|
||||||
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
|
bool check(const Ts &...x) override { return this->parent_->get_active_page() == this->page_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Display *parent_;
|
Display *parent_;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ template<typename... Ts> class UpAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->up(); }
|
void play(const Ts &...x) override { this->menu_->up(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -20,7 +20,7 @@ template<typename... Ts> class DownAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->down(); }
|
void play(const Ts &...x) override { this->menu_->down(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -30,7 +30,7 @@ template<typename... Ts> class LeftAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->left(); }
|
void play(const Ts &...x) override { this->menu_->left(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -40,7 +40,7 @@ template<typename... Ts> class RightAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->right(); }
|
void play(const Ts &...x) override { this->menu_->right(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -50,7 +50,7 @@ template<typename... Ts> class EnterAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->enter(); }
|
void play(const Ts &...x) override { this->menu_->enter(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -60,7 +60,7 @@ template<typename... Ts> class ShowAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->show(); }
|
void play(const Ts &...x) override { this->menu_->show(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -70,7 +70,7 @@ template<typename... Ts> class HideAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->hide(); }
|
void play(const Ts &...x) override { this->menu_->hide(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -80,7 +80,7 @@ template<typename... Ts> class ShowMainAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->menu_->show_main(); }
|
void play(const Ts &...x) override { this->menu_->show_main(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
@@ -88,7 +88,7 @@ template<typename... Ts> class ShowMainAction : public Action<Ts...> {
|
|||||||
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
|
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
|
explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||||
bool check(Ts... x) override { return this->menu_->is_active(); }
|
bool check(const Ts &...x) override { return this->menu_->is_active(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DisplayMenuComponent *menu_;
|
DisplayMenuComponent *menu_;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ std::string MenuItemSelect::get_value_text() const {
|
|||||||
result = this->value_getter_.value()(this);
|
result = this->value_getter_.value()(this);
|
||||||
} else {
|
} else {
|
||||||
if (this->select_var_ != nullptr) {
|
if (this->select_var_ != nullptr) {
|
||||||
result = this->select_var_->state;
|
result = this->select_var_->current_option();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,12 +59,12 @@ class DS1307Component : public time::RealTimeClock, public i2c::I2CDevice {
|
|||||||
|
|
||||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<DS1307Component> {
|
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<DS1307Component> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { this->parent_->write_time(); }
|
void play(const Ts &...x) override { this->parent_->write_time(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<DS1307Component> {
|
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<DS1307Component> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { this->parent_->read_time(); }
|
void play(const Ts &...x) override { this->parent_->read_time(); }
|
||||||
};
|
};
|
||||||
} // namespace ds1307
|
} // namespace ds1307
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ class DutyTimeSensor : public sensor::Sensor, public PollingComponent {
|
|||||||
template<typename... Ts> class BaseAction : public Action<Ts...>, public Parented<DutyTimeSensor> {};
|
template<typename... Ts> class BaseAction : public Action<Ts...>, public Parented<DutyTimeSensor> {};
|
||||||
|
|
||||||
template<typename... Ts> class StartAction : public BaseAction<Ts...> {
|
template<typename... Ts> class StartAction : public BaseAction<Ts...> {
|
||||||
void play(Ts... x) override { this->parent_->start(); }
|
void play(const Ts &...x) override { this->parent_->start(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class StopAction : public BaseAction<Ts...> {
|
template<typename... Ts> class StopAction : public BaseAction<Ts...> {
|
||||||
void play(Ts... x) override { this->parent_->stop(); }
|
void play(const Ts &...x) override { this->parent_->stop(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ResetAction : public BaseAction<Ts...> {
|
template<typename... Ts> class ResetAction : public BaseAction<Ts...> {
|
||||||
void play(Ts... x) override { this->parent_->reset(); }
|
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class RunningCondition : public Condition<Ts...>, public Parented<DutyTimeSensor> {
|
template<typename... Ts> class RunningCondition : public Condition<Ts...>, public Parented<DutyTimeSensor> {
|
||||||
@@ -67,7 +67,7 @@ template<typename... Ts> class RunningCondition : public Condition<Ts...>, publi
|
|||||||
explicit RunningCondition(DutyTimeSensor *parent, bool state) : Parented(parent), state_(state) {}
|
explicit RunningCondition(DutyTimeSensor *parent, bool state) : Parented(parent), state_(state) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool check(Ts... x) override { return this->parent_->is_running() == this->state_; }
|
bool check(const Ts &...x) override { return this->parent_->is_running() == this->state_; }
|
||||||
bool state_;
|
bool state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "e131_addressable_light_effect.h"
|
#include "e131_addressable_light_effect.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace e131 {
|
namespace e131 {
|
||||||
|
|
||||||
@@ -76,14 +78,14 @@ void E131Component::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
||||||
if (light_effects_.count(light_effect)) {
|
if (std::find(light_effects_.begin(), light_effects_.end(), light_effect) != light_effects_.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||||
light_effect->get_last_universe());
|
light_effect->get_last_universe());
|
||||||
|
|
||||||
light_effects_.insert(light_effect);
|
light_effects_.push_back(light_effect);
|
||||||
|
|
||||||
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
|
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
|
||||||
join_(universe);
|
join_(universe);
|
||||||
@@ -91,14 +93,17 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
|
void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
|
||||||
if (!light_effects_.count(light_effect)) {
|
auto it = std::find(light_effects_.begin(), light_effects_.end(), light_effect);
|
||||||
|
if (it == light_effects_.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||||
light_effect->get_last_universe());
|
light_effect->get_last_universe());
|
||||||
|
|
||||||
light_effects_.erase(light_effect);
|
// Swap with last element and pop for O(1) removal (order doesn't matter)
|
||||||
|
*it = light_effects_.back();
|
||||||
|
light_effects_.pop_back();
|
||||||
|
|
||||||
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
|
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
|
||||||
leave_(universe);
|
leave_(universe);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -47,9 +46,8 @@ class E131Component : public esphome::Component {
|
|||||||
|
|
||||||
E131ListenMethod listen_method_{E131_MULTICAST};
|
E131ListenMethod listen_method_{E131_MULTICAST};
|
||||||
std::unique_ptr<socket::Socket> socket_;
|
std::unique_ptr<socket::Socket> socket_;
|
||||||
std::set<E131AddressableLightEffect *> light_effects_;
|
std::vector<E131AddressableLightEffect *> light_effects_;
|
||||||
std::map<int, int> universe_consumers_;
|
std::map<int, int> universe_consumers_;
|
||||||
std::map<int, E131Packet> universe_packets_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace e131
|
} // namespace e131
|
||||||
|
|||||||
@@ -1,21 +1,35 @@
|
|||||||
|
import importlib
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
from esphome import core, pins
|
from esphome import core, pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import display, spi
|
from esphome.components import display, spi
|
||||||
|
from esphome.components.mipi import flatten_sequence, map_sequence
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BUSY_PIN,
|
CONF_BUSY_PIN,
|
||||||
|
CONF_CS_PIN,
|
||||||
|
CONF_DATA_RATE,
|
||||||
CONF_DC_PIN,
|
CONF_DC_PIN,
|
||||||
|
CONF_DIMENSIONS,
|
||||||
|
CONF_ENABLE_PIN,
|
||||||
|
CONF_HEIGHT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_INIT_SEQUENCE,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
CONF_PAGES,
|
|
||||||
CONF_RESET_DURATION,
|
CONF_RESET_DURATION,
|
||||||
CONF_RESET_PIN,
|
CONF_RESET_PIN,
|
||||||
|
CONF_WIDTH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
AUTO_LOAD = ["split_buffer"]
|
AUTO_LOAD = ["split_buffer"]
|
||||||
DEPENDENCIES = ["spi"]
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
CONF_INIT_SEQUENCE_ID = "init_sequence_id"
|
||||||
|
|
||||||
epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi")
|
epaper_spi_ns = cg.esphome_ns.namespace("epaper_spi")
|
||||||
EPaperBase = epaper_spi_ns.class_(
|
EPaperBase = epaper_spi_ns.class_(
|
||||||
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
"EPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
|
||||||
@@ -24,30 +38,79 @@ EPaperBase = epaper_spi_ns.class_(
|
|||||||
EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase)
|
EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase)
|
||||||
EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6)
|
EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6)
|
||||||
|
|
||||||
MODELS = {
|
|
||||||
"7.3in-spectra-e6": EPaper7p3InSpectraE6,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# Import all models dynamically from the models package
|
||||||
|
for module_info in pkgutil.iter_modules(models.__path__):
|
||||||
|
importlib.import_module(f".models.{module_info.name}", package=__package__)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
MODELS = models.EpaperModel.models
|
||||||
display.FULL_DISPLAY_SCHEMA.extend(
|
|
||||||
{
|
DIMENSION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(EPaperBase),
|
{
|
||||||
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_WIDTH): cv.int_,
|
||||||
cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True, space="-"),
|
cv.Required(CONF_HEIGHT): cv.int_,
|
||||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
}
|
||||||
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
|
|
||||||
cv.Optional(CONF_RESET_DURATION): cv.All(
|
|
||||||
cv.positive_time_period_milliseconds,
|
|
||||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.polling_component_schema("60s"))
|
|
||||||
.extend(spi.spi_device_schema()),
|
|
||||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def model_schema(config):
|
||||||
|
model = MODELS[config[CONF_MODEL]]
|
||||||
|
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
|
||||||
|
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
|
||||||
|
return (
|
||||||
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
|
spi.spi_device_schema(
|
||||||
|
cs_pin_required=False,
|
||||||
|
default_mode="MODE0",
|
||||||
|
default_data_rate=model.get_default(CONF_DATA_RATE, 10_000_000),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
model.option(pin): pins.gpio_output_pin_schema
|
||||||
|
for pin in (CONF_RESET_PIN, CONF_CS_PIN, CONF_BUSY_PIN)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||||
|
model.option(CONF_DC_PIN, fallback=None): pins.gpio_output_pin_schema,
|
||||||
|
cv.GenerateID(): cv.declare_id(class_name),
|
||||||
|
cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8),
|
||||||
|
cv_dimensions(CONF_DIMENSIONS): DIMENSION_SCHEMA,
|
||||||
|
model.option(CONF_ENABLE_PIN): cv.ensure_list(
|
||||||
|
pins.gpio_output_pin_schema
|
||||||
|
),
|
||||||
|
model.option(CONF_INIT_SEQUENCE, cv.UNDEFINED): cv.ensure_list(
|
||||||
|
map_sequence
|
||||||
|
),
|
||||||
|
model.option(CONF_RESET_DURATION, cv.UNDEFINED): cv.All(
|
||||||
|
cv.positive_time_period_milliseconds,
|
||||||
|
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def customise_schema(config):
|
||||||
|
"""
|
||||||
|
Create a customised config schema for a specific model and validate the configuration.
|
||||||
|
:param config: The configuration dictionary to validate
|
||||||
|
:return: The validated configuration dictionary
|
||||||
|
:raises cv.Invalid: If the configuration is invalid
|
||||||
|
"""
|
||||||
|
config = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
|
||||||
|
},
|
||||||
|
extra=cv.ALLOW_EXTRA,
|
||||||
|
)(config)
|
||||||
|
return model_schema(config)(config)
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = customise_schema
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
||||||
"epaper_spi", require_miso=False, require_mosi=True
|
"epaper_spi", require_miso=False, require_mosi=True
|
||||||
)
|
)
|
||||||
@@ -56,8 +119,23 @@ FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
model = MODELS[config[CONF_MODEL]]
|
model = MODELS[config[CONF_MODEL]]
|
||||||
|
|
||||||
rhs = model.new()
|
init_sequence = config.get(CONF_INIT_SEQUENCE)
|
||||||
var = cg.Pvariable(config[CONF_ID], rhs, model)
|
if init_sequence is None:
|
||||||
|
init_sequence = model.get_init_sequence(config)
|
||||||
|
init_sequence = flatten_sequence(init_sequence)
|
||||||
|
init_sequence_length = len(init_sequence)
|
||||||
|
init_sequence_id = cg.static_const_array(
|
||||||
|
config[CONF_INIT_SEQUENCE_ID], init_sequence
|
||||||
|
)
|
||||||
|
width, height = model.get_dimensions(config)
|
||||||
|
var = cg.new_Pvariable(
|
||||||
|
config[CONF_ID],
|
||||||
|
model.name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
init_sequence_id,
|
||||||
|
init_sequence_length,
|
||||||
|
)
|
||||||
|
|
||||||
await display.register_display(var, config)
|
await display.register_display(var, config)
|
||||||
await spi.register_spi_device(var, config)
|
await spi.register_spi_device(var, config)
|
||||||
|
|||||||
@@ -8,33 +8,20 @@ namespace esphome::epaper_spi {
|
|||||||
|
|
||||||
static const char *const TAG = "epaper_spi";
|
static const char *const TAG = "epaper_spi";
|
||||||
|
|
||||||
static const LogString *epaper_state_to_string(EPaperState state) {
|
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
|
||||||
switch (state) {
|
"IDLE", "UPDATE", "RESET", "RESET_END",
|
||||||
case EPaperState::IDLE:
|
|
||||||
return LOG_STR("IDLE");
|
"SHOULD_WAIT", "INITIALISE", "TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
|
||||||
case EPaperState::UPDATE:
|
};
|
||||||
return LOG_STR("UPDATE");
|
|
||||||
case EPaperState::RESET:
|
const char *EPaperBase::epaper_state_to_string_() {
|
||||||
return LOG_STR("RESET");
|
if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
|
||||||
case EPaperState::INITIALISE:
|
return EPAPER_STATE_STRINGS[idx];
|
||||||
return LOG_STR("INITIALISE");
|
return "Unknown";
|
||||||
case EPaperState::TRANSFER_DATA:
|
|
||||||
return LOG_STR("TRANSFER_DATA");
|
|
||||||
case EPaperState::POWER_ON:
|
|
||||||
return LOG_STR("POWER_ON");
|
|
||||||
case EPaperState::REFRESH_SCREEN:
|
|
||||||
return LOG_STR("REFRESH_SCREEN");
|
|
||||||
case EPaperState::POWER_OFF:
|
|
||||||
return LOG_STR("POWER_OFF");
|
|
||||||
case EPaperState::DEEP_SLEEP:
|
|
||||||
return LOG_STR("DEEP_SLEEP");
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::setup() {
|
void EPaperBase::setup() {
|
||||||
if (!this->init_buffer_(this->get_buffer_length())) {
|
if (!this->init_buffer_(this->buffer_length_)) {
|
||||||
this->mark_failed("Failed to initialise buffer");
|
this->mark_failed("Failed to initialise buffer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -50,7 +37,7 @@ bool EPaperBase::init_buffer_(size_t buffer_length) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::setup_pins_() {
|
void EPaperBase::setup_pins_() const {
|
||||||
this->dc_pin_->setup(); // OUTPUT
|
this->dc_pin_->setup(); // OUTPUT
|
||||||
this->dc_pin_->digital_write(false);
|
this->dc_pin_->digital_write(false);
|
||||||
|
|
||||||
@@ -81,11 +68,7 @@ void EPaperBase::data(uint8_t value) {
|
|||||||
// write a command followed by zero or more bytes of data.
|
// write a command followed by zero or more bytes of data.
|
||||||
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
|
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
|
||||||
// [COMMAND, LENGTH, DATA...]
|
// [COMMAND, LENGTH, DATA...]
|
||||||
void EPaperBase::cmd_data(const uint8_t *data) {
|
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
|
||||||
const uint8_t command = data[0];
|
|
||||||
const uint8_t length = data[1];
|
|
||||||
const uint8_t *ptr = data + 2;
|
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
|
ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
|
||||||
format_hex_pretty(ptr, length, '.', false).c_str());
|
format_hex_pretty(ptr, length, '.', false).c_str());
|
||||||
|
|
||||||
@@ -99,91 +82,146 @@ void EPaperBase::cmd_data(const uint8_t *data) {
|
|||||||
this->disable();
|
this->disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EPaperBase::is_idle_() {
|
bool EPaperBase::is_idle_() const {
|
||||||
if (this->busy_pin_ == nullptr) {
|
if (this->busy_pin_ == nullptr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this->busy_pin_->digital_read();
|
return !this->busy_pin_->digital_read();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::reset() {
|
bool EPaperBase::reset_() const {
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->digital_write(false);
|
if (this->state_ == EPaperState::RESET) {
|
||||||
this->disable_loop();
|
this->reset_pin_->digital_write(false);
|
||||||
this->set_timeout(this->reset_duration_, [this] {
|
return false;
|
||||||
this->reset_pin_->digital_write(true);
|
}
|
||||||
this->set_timeout(20, [this] { this->enable_loop(); });
|
this->reset_pin_->digital_write(true);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::update() {
|
void EPaperBase::update() {
|
||||||
if (!this->state_queue_.empty()) {
|
if (this->state_ != EPaperState::IDLE) {
|
||||||
ESP_LOGE(TAG, "Display update already in progress - %s",
|
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
|
||||||
LOG_STR_ARG(epaper_state_to_string(this->state_queue_.front())));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->set_state_(EPaperState::RESET);
|
||||||
this->state_queue_.push(EPaperState::UPDATE);
|
|
||||||
this->state_queue_.push(EPaperState::RESET);
|
|
||||||
this->state_queue_.push(EPaperState::INITIALISE);
|
|
||||||
this->state_queue_.push(EPaperState::TRANSFER_DATA);
|
|
||||||
this->state_queue_.push(EPaperState::POWER_ON);
|
|
||||||
this->state_queue_.push(EPaperState::REFRESH_SCREEN);
|
|
||||||
this->state_queue_.push(EPaperState::POWER_OFF);
|
|
||||||
this->state_queue_.push(EPaperState::DEEP_SLEEP);
|
|
||||||
this->state_queue_.push(EPaperState::IDLE);
|
|
||||||
|
|
||||||
this->enable_loop();
|
this->enable_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EPaperBase::wait_for_idle_(bool should_wait) {
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
|
if (should_wait) {
|
||||||
|
this->waiting_for_idle_start_ = millis();
|
||||||
|
this->waiting_for_idle_last_print_ = this->waiting_for_idle_start_;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
this->waiting_for_idle_ = should_wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called during the loop task.
|
||||||
|
* First defer for any pending delays, then check if we are waiting for the display to become idle.
|
||||||
|
* If not waiting for idle, process the state machine.
|
||||||
|
*/
|
||||||
|
|
||||||
void EPaperBase::loop() {
|
void EPaperBase::loop() {
|
||||||
|
auto now = millis();
|
||||||
|
if (this->delay_until_ != 0) {
|
||||||
|
// using modulus arithmetic to handle wrap-around
|
||||||
|
int diff = now - this->delay_until_;
|
||||||
|
if (diff < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->delay_until_ = 0;
|
||||||
|
}
|
||||||
if (this->waiting_for_idle_) {
|
if (this->waiting_for_idle_) {
|
||||||
if (this->is_idle_()) {
|
if (this->is_idle_()) {
|
||||||
this->waiting_for_idle_ = false;
|
this->waiting_for_idle_ = false;
|
||||||
|
ESP_LOGV(TAG, "Screen now idle after %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
|
||||||
} else {
|
} else {
|
||||||
if (App.get_loop_component_start_time() - this->waiting_for_idle_last_print_ >= 1000) {
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
ESP_LOGV(TAG, "Waiting for idle");
|
if (now - this->waiting_for_idle_last_print_ >= 1000) {
|
||||||
this->waiting_for_idle_last_print_ = App.get_loop_component_start_time();
|
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
|
||||||
|
this->waiting_for_idle_last_print_ = millis();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this->process_state_();
|
||||||
|
}
|
||||||
|
|
||||||
auto state = this->state_queue_.front();
|
/**
|
||||||
|
* Process the state machine.
|
||||||
switch (state) {
|
* Typical state sequence:
|
||||||
|
* IDLE -> RESET -> RESET_END -> UPDATE -> INITIALISE -> TRANSFER_DATA -> POWER_ON -> REFRESH_SCREEN -> POWER_OFF ->
|
||||||
|
* DEEP_SLEEP -> IDLE
|
||||||
|
*
|
||||||
|
* Should a subclassed class need to override this, the method will need to be made virtual.
|
||||||
|
*/
|
||||||
|
void EPaperBase::process_state_() {
|
||||||
|
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
|
||||||
|
switch (this->state_) {
|
||||||
|
default:
|
||||||
|
ESP_LOGD(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
|
||||||
|
this->disable_loop();
|
||||||
|
break;
|
||||||
case EPaperState::IDLE:
|
case EPaperState::IDLE:
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
break;
|
break;
|
||||||
|
case EPaperState::RESET:
|
||||||
|
case EPaperState::RESET_END:
|
||||||
|
if (this->reset_()) {
|
||||||
|
this->set_state_(EPaperState::UPDATE);
|
||||||
|
} else {
|
||||||
|
this->set_state_(EPaperState::RESET_END);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case EPaperState::UPDATE:
|
case EPaperState::UPDATE:
|
||||||
this->do_update_(); // Calls ESPHome (current page) lambda
|
this->do_update_(); // Calls ESPHome (current page) lambda
|
||||||
break;
|
this->set_state_(EPaperState::INITIALISE);
|
||||||
case EPaperState::RESET:
|
|
||||||
this->reset();
|
|
||||||
break;
|
break;
|
||||||
case EPaperState::INITIALISE:
|
case EPaperState::INITIALISE:
|
||||||
this->initialise_();
|
this->initialise_();
|
||||||
|
this->set_state_(EPaperState::TRANSFER_DATA);
|
||||||
break;
|
break;
|
||||||
case EPaperState::TRANSFER_DATA:
|
case EPaperState::TRANSFER_DATA:
|
||||||
if (!this->transfer_data()) {
|
if (!this->transfer_data()) {
|
||||||
return; // Not done yet, come back next loop
|
return; // Not done yet, come back next loop
|
||||||
}
|
}
|
||||||
|
this->set_state_(EPaperState::POWER_ON);
|
||||||
break;
|
break;
|
||||||
case EPaperState::POWER_ON:
|
case EPaperState::POWER_ON:
|
||||||
this->power_on();
|
this->power_on();
|
||||||
|
this->set_state_(EPaperState::REFRESH_SCREEN);
|
||||||
break;
|
break;
|
||||||
case EPaperState::REFRESH_SCREEN:
|
case EPaperState::REFRESH_SCREEN:
|
||||||
this->refresh_screen();
|
this->refresh_screen();
|
||||||
|
this->set_state_(EPaperState::POWER_OFF);
|
||||||
break;
|
break;
|
||||||
case EPaperState::POWER_OFF:
|
case EPaperState::POWER_OFF:
|
||||||
this->power_off();
|
this->power_off();
|
||||||
|
this->set_state_(EPaperState::DEEP_SLEEP);
|
||||||
break;
|
break;
|
||||||
case EPaperState::DEEP_SLEEP:
|
case EPaperState::DEEP_SLEEP:
|
||||||
this->deep_sleep();
|
this->deep_sleep();
|
||||||
|
this->set_state_(EPaperState::IDLE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->state_queue_.pop();
|
}
|
||||||
|
|
||||||
|
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||||
|
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
|
||||||
|
this->state_ = state;
|
||||||
|
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
|
||||||
|
if (delay != 0) {
|
||||||
|
this->delay_until_ = millis() + delay;
|
||||||
|
} else {
|
||||||
|
this->delay_until_ = 0;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
|
||||||
|
TRUEFALSE(this->waiting_for_idle_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::start_command_() {
|
void EPaperBase::start_command_() {
|
||||||
@@ -203,25 +241,39 @@ void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
|
|||||||
|
|
||||||
void EPaperBase::initialise_() {
|
void EPaperBase::initialise_() {
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
const auto &sequence = this->init_sequence_;
|
|
||||||
const size_t sequence_size = this->init_sequence_length_;
|
|
||||||
while (index != sequence_size) {
|
|
||||||
if (sequence_size - index < 2) {
|
|
||||||
this->mark_failed("Malformed init sequence");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto *ptr = sequence + index;
|
|
||||||
const uint8_t length = ptr[1];
|
|
||||||
if (sequence_size - index < length + 2) {
|
|
||||||
this->mark_failed("Malformed init sequence");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->cmd_data(ptr);
|
auto *sequence = this->init_sequence_;
|
||||||
index += length + 2;
|
auto length = this->init_sequence_length_;
|
||||||
|
while (index != length) {
|
||||||
|
if (length - index < 2) {
|
||||||
|
this->mark_failed("Malformed init sequence");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint8_t cmd = sequence[index++];
|
||||||
|
if (const uint8_t x = sequence[index++]; x == DELAY_FLAG) {
|
||||||
|
ESP_LOGV(TAG, "Delay %dms", cmd);
|
||||||
|
delay(cmd);
|
||||||
|
} else {
|
||||||
|
const uint8_t num_args = x & 0x7F;
|
||||||
|
if (length - index < num_args) {
|
||||||
|
ESP_LOGE(TAG, "Malformed init sequence, cmd = %X, num_args = %u", cmd, num_args);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Command %02X, length %d", cmd, num_args);
|
||||||
|
this->cmd_data(cmd, sequence + index, num_args);
|
||||||
|
index += num_args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->power_on();
|
void EPaperBase::dump_config() {
|
||||||
|
LOG_DISPLAY("", "E-Paper SPI", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: %s", this->name_);
|
||||||
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
} // namespace esphome::epaper_spi
|
||||||
|
|||||||
@@ -8,36 +8,48 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
namespace esphome::epaper_spi {
|
namespace esphome::epaper_spi {
|
||||||
|
using namespace display;
|
||||||
|
|
||||||
enum class EPaperState : uint8_t {
|
enum class EPaperState : uint8_t {
|
||||||
IDLE,
|
IDLE, // not doing anything
|
||||||
UPDATE,
|
UPDATE, // update the buffer
|
||||||
RESET,
|
RESET, // drive reset low (active)
|
||||||
INITIALISE,
|
RESET_END, // drive reset high (inactive)
|
||||||
TRANSFER_DATA,
|
|
||||||
POWER_ON,
|
SHOULD_WAIT, // states higher than this should wait for the display to be not busy
|
||||||
REFRESH_SCREEN,
|
INITIALISE, // send the init sequence
|
||||||
POWER_OFF,
|
TRANSFER_DATA, // transfer data to the display
|
||||||
DEEP_SLEEP,
|
POWER_ON, // power on the display
|
||||||
|
REFRESH_SCREEN, // send refresh command
|
||||||
|
POWER_OFF, // power off the display
|
||||||
|
DEEP_SLEEP, // deep sleep the display
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
|
static constexpr uint8_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
|
||||||
|
static constexpr uint8_t DELAY_FLAG = 0xFF;
|
||||||
|
|
||||||
class EPaperBase : public display::DisplayBuffer,
|
class EPaperBase : public DisplayBuffer,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||||
spi::DATA_RATE_2MHZ> {
|
spi::DATA_RATE_2MHZ> {
|
||||||
public:
|
public:
|
||||||
EPaperBase(const uint8_t *init_sequence, const size_t init_sequence_length)
|
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||||
: init_sequence_length_(init_sequence_length), init_sequence_(init_sequence) {}
|
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY)
|
||||||
|
: name_(name),
|
||||||
|
width_(width),
|
||||||
|
height_(height),
|
||||||
|
init_sequence_(init_sequence),
|
||||||
|
init_sequence_length_(init_sequence_length),
|
||||||
|
display_type_(display_type) {}
|
||||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||||
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
|
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
|
||||||
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
|
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
void command(uint8_t value);
|
void command(uint8_t value);
|
||||||
void data(uint8_t value);
|
void data(uint8_t value);
|
||||||
void cmd_data(const uint8_t *data);
|
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length);
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
@@ -46,48 +58,84 @@ class EPaperBase : public display::DisplayBuffer,
|
|||||||
|
|
||||||
void on_safe_shutdown() override;
|
void on_safe_shutdown() override;
|
||||||
|
|
||||||
|
DisplayType get_display_type() override { return this->display_type_; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool is_idle_();
|
int get_height_internal() override { return this->height_; };
|
||||||
void setup_pins_();
|
int get_width_internal() override { return this->width_; };
|
||||||
virtual void reset();
|
void process_state_();
|
||||||
|
|
||||||
|
const char *epaper_state_to_string_();
|
||||||
|
bool is_idle_() const;
|
||||||
|
void setup_pins_() const;
|
||||||
|
bool reset_() const;
|
||||||
void initialise_();
|
void initialise_();
|
||||||
|
void wait_for_idle_(bool should_wait);
|
||||||
bool init_buffer_(size_t buffer_length);
|
bool init_buffer_(size_t buffer_length);
|
||||||
|
|
||||||
virtual int get_width_controller() { return this->get_width_internal(); };
|
virtual int get_width_controller() { return this->get_width_internal(); };
|
||||||
virtual void deep_sleep() = 0;
|
|
||||||
|
/**
|
||||||
|
* Methods that must be implemented by concrete classes to control the display
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Send data to the device via SPI
|
* Send data to the device via SPI
|
||||||
* @return true if done, false if should be called next loop
|
* @return true if done, false if it should be called next loop
|
||||||
*/
|
*/
|
||||||
virtual bool transfer_data() = 0;
|
virtual bool transfer_data() = 0;
|
||||||
|
/**
|
||||||
|
* Refresh the screen after data transfer
|
||||||
|
*/
|
||||||
virtual void refresh_screen() = 0;
|
virtual void refresh_screen() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Power the display on
|
||||||
|
*/
|
||||||
virtual void power_on() = 0;
|
virtual void power_on() = 0;
|
||||||
|
/**
|
||||||
|
* Power the display off
|
||||||
|
*/
|
||||||
virtual void power_off() = 0;
|
virtual void power_off() = 0;
|
||||||
virtual uint32_t get_buffer_length() = 0;
|
|
||||||
|
/**
|
||||||
|
* Place the display into deep sleep
|
||||||
|
*/
|
||||||
|
virtual void deep_sleep() = 0;
|
||||||
|
|
||||||
|
void set_state_(EPaperState state, uint16_t delay = 0);
|
||||||
|
|
||||||
void start_command_();
|
void start_command_();
|
||||||
void end_command_();
|
void end_command_();
|
||||||
void start_data_();
|
void start_data_();
|
||||||
void end_data_();
|
void end_data_();
|
||||||
|
|
||||||
const size_t init_sequence_length_{0};
|
// properties initialised in the constructor
|
||||||
|
const char *name_;
|
||||||
|
uint16_t width_;
|
||||||
|
uint16_t height_;
|
||||||
|
const uint8_t *init_sequence_;
|
||||||
|
size_t init_sequence_length_;
|
||||||
|
DisplayType display_type_;
|
||||||
|
|
||||||
size_t current_data_index_{0};
|
size_t buffer_length_{};
|
||||||
|
size_t current_data_index_{0}; // used by data transfer to track progress
|
||||||
uint32_t reset_duration_{200};
|
uint32_t reset_duration_{200};
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
|
uint32_t transfer_start_time_{};
|
||||||
uint32_t waiting_for_idle_last_print_{0};
|
uint32_t waiting_for_idle_last_print_{0};
|
||||||
|
uint32_t waiting_for_idle_start_{0};
|
||||||
|
#endif
|
||||||
|
|
||||||
GPIOPin *dc_pin_;
|
GPIOPin *dc_pin_{};
|
||||||
GPIOPin *busy_pin_{nullptr};
|
GPIOPin *busy_pin_{};
|
||||||
GPIOPin *reset_pin_{nullptr};
|
GPIOPin *reset_pin_{};
|
||||||
|
|
||||||
const uint8_t *init_sequence_{nullptr};
|
|
||||||
|
|
||||||
bool waiting_for_idle_{false};
|
bool waiting_for_idle_{false};
|
||||||
|
uint32_t delay_until_{0};
|
||||||
|
|
||||||
split_buffer::SplitBuffer buffer_;
|
split_buffer::SplitBuffer buffer_;
|
||||||
|
|
||||||
std::queue<EPaperState> state_queue_{{EPaperState::IDLE}};
|
EPaperState state_{EPaperState::IDLE};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
} // namespace esphome::epaper_spi
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#include "epaper_spi_model_7p3in_spectra_e6.h"
|
|
||||||
|
|
||||||
namespace esphome::epaper_spi {
|
|
||||||
|
|
||||||
static constexpr const char *const TAG = "epaper_spi.7.3in-spectra-e6";
|
|
||||||
|
|
||||||
void EPaper7p3InSpectraE6::power_on() {
|
|
||||||
ESP_LOGI(TAG, "Power on");
|
|
||||||
this->command(0x04);
|
|
||||||
this->waiting_for_idle_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EPaper7p3InSpectraE6::power_off() {
|
|
||||||
ESP_LOGI(TAG, "Power off");
|
|
||||||
this->command(0x02);
|
|
||||||
this->data(0x00);
|
|
||||||
this->waiting_for_idle_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EPaper7p3InSpectraE6::refresh_screen() {
|
|
||||||
ESP_LOGI(TAG, "Refresh");
|
|
||||||
this->command(0x12);
|
|
||||||
this->data(0x00);
|
|
||||||
this->waiting_for_idle_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EPaper7p3InSpectraE6::deep_sleep() {
|
|
||||||
ESP_LOGI(TAG, "Deep sleep");
|
|
||||||
this->command(0x07);
|
|
||||||
this->data(0xA5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EPaper7p3InSpectraE6::dump_config() {
|
|
||||||
LOG_DISPLAY("", "E-Paper SPI", this);
|
|
||||||
ESP_LOGCONFIG(TAG, " Model: 7.3in Spectra E6");
|
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
|
||||||
LOG_UPDATE_INTERVAL(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "epaper_spi_spectra_e6.h"
|
|
||||||
|
|
||||||
namespace esphome::epaper_spi {
|
|
||||||
|
|
||||||
class EPaper7p3InSpectraE6 : public EPaperSpectraE6 {
|
|
||||||
static constexpr const uint16_t WIDTH = 800;
|
|
||||||
static constexpr const uint16_t HEIGHT = 480;
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
// Command, data length, data
|
|
||||||
static constexpr uint8_t INIT_SEQUENCE[] = {
|
|
||||||
0xAA, 6, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18,
|
|
||||||
0x01, 1, 0x3F,
|
|
||||||
0x00, 2, 0x5F, 0x69,
|
|
||||||
0x03, 4, 0x00, 0x54, 0x00, 0x44,
|
|
||||||
0x05, 4, 0x40, 0x1F, 0x1F, 0x2C,
|
|
||||||
0x06, 4, 0x6F, 0x1F, 0x17, 0x49,
|
|
||||||
0x08, 4, 0x6F, 0x1F, 0x1F, 0x22,
|
|
||||||
0x30, 1, 0x03,
|
|
||||||
0x50, 1, 0x3F,
|
|
||||||
0x60, 2, 0x02, 0x00,
|
|
||||||
0x61, 4, WIDTH / 256, WIDTH % 256, HEIGHT / 256, HEIGHT % 256,
|
|
||||||
0x84, 1, 0x01,
|
|
||||||
0xE3, 1, 0x2F,
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
public:
|
|
||||||
EPaper7p3InSpectraE6() : EPaperSpectraE6(INIT_SEQUENCE, sizeof(INIT_SEQUENCE)) {}
|
|
||||||
|
|
||||||
void dump_config() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int get_width_internal() override { return WIDTH; };
|
|
||||||
int get_height_internal() override { return HEIGHT; };
|
|
||||||
|
|
||||||
void refresh_screen() override;
|
|
||||||
void power_on() override;
|
|
||||||
void power_off() override;
|
|
||||||
void deep_sleep() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
|
||||||
@@ -1,135 +1,166 @@
|
|||||||
#include "epaper_spi_spectra_e6.h"
|
#include "epaper_spi_spectra_e6.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::epaper_spi {
|
namespace esphome::epaper_spi {
|
||||||
|
|
||||||
static constexpr const char *const TAG = "epaper_spi.6c";
|
static constexpr const char *const TAG = "epaper_spi.6c";
|
||||||
|
static constexpr size_t MAX_TRANSFER_SIZE = 128;
|
||||||
|
static constexpr unsigned char GRAY_THRESHOLD = 50;
|
||||||
|
|
||||||
static inline uint8_t color_to_hex(Color color) {
|
enum E6Color {
|
||||||
if (color.red > 127) {
|
BLACK,
|
||||||
if (color.green > 170) {
|
WHITE,
|
||||||
if (color.blue > 127) {
|
YELLOW,
|
||||||
return 0x1; // White
|
RED,
|
||||||
} else {
|
SKIP_1,
|
||||||
return 0x2; // Yellow
|
BLUE,
|
||||||
}
|
GREEN,
|
||||||
} else {
|
CYAN,
|
||||||
return 0x3; // Red (or Magenta)
|
SKIP_2,
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
if (color.green > 127) {
|
static uint8_t color_to_hex(Color color) {
|
||||||
if (color.blue > 127) {
|
// --- Step 1: Check for Grayscale (Black or White) ---
|
||||||
return 0x5; // Cyan -> Blue
|
// We define "grayscale" as a color where the min and max components
|
||||||
} else {
|
// are close to each other.
|
||||||
return 0x6; // Green
|
unsigned char max_rgb = std::max({color.r, color.g, color.b});
|
||||||
}
|
unsigned char min_rgb = std::min({color.r, color.g, color.b});
|
||||||
} else {
|
|
||||||
if (color.blue > 127) {
|
if ((max_rgb - min_rgb) < GRAY_THRESHOLD) {
|
||||||
return 0x5; // Blue
|
// It's a shade of gray. Map to BLACK or WHITE.
|
||||||
} else {
|
// We split the luminance at the halfway point (382 = (255*3)/2)
|
||||||
return 0x0; // Black
|
if ((static_cast<int>(color.r) + color.g + color.b) > 382) {
|
||||||
}
|
return WHITE;
|
||||||
}
|
}
|
||||||
|
return BLACK;
|
||||||
}
|
}
|
||||||
|
// --- Step 2: Check for Primary/Secondary Colors ---
|
||||||
|
// If it's not gray, it's a color. We check which components are
|
||||||
|
// "on" (over 128) vs "off". This divides the RGB cube into 8 corners.
|
||||||
|
bool r_on = (color.r > 128);
|
||||||
|
bool g_on = (color.g > 128);
|
||||||
|
bool b_on = (color.b > 128);
|
||||||
|
|
||||||
|
if (r_on && g_on && !b_on) {
|
||||||
|
return YELLOW;
|
||||||
|
}
|
||||||
|
if (r_on && !g_on && !b_on) {
|
||||||
|
return RED;
|
||||||
|
}
|
||||||
|
if (!r_on && g_on && !b_on) {
|
||||||
|
return GREEN;
|
||||||
|
}
|
||||||
|
if (!r_on && !g_on && b_on) {
|
||||||
|
return BLUE;
|
||||||
|
}
|
||||||
|
// Handle "impure" colors (Cyan, Magenta)
|
||||||
|
if (!r_on && g_on && b_on) {
|
||||||
|
// Cyan (G+B) -> Closest is Green or Blue. Pick Green.
|
||||||
|
return GREEN;
|
||||||
|
}
|
||||||
|
if (r_on && !g_on) {
|
||||||
|
// Magenta (R+B) -> Closest is Red or Blue. Pick Red.
|
||||||
|
return RED;
|
||||||
|
}
|
||||||
|
// Handle the remaining corners (White-ish, Black-ish)
|
||||||
|
if (r_on) {
|
||||||
|
// All high (but not gray) -> White
|
||||||
|
return WHITE;
|
||||||
|
}
|
||||||
|
// !r_on && !g_on && !b_on
|
||||||
|
// All low (but not gray) -> Black
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperSpectraE6::power_on() {
|
||||||
|
ESP_LOGD(TAG, "Power on");
|
||||||
|
this->command(0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperSpectraE6::power_off() {
|
||||||
|
ESP_LOGD(TAG, "Power off");
|
||||||
|
this->command(0x02);
|
||||||
|
this->data(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperSpectraE6::refresh_screen() {
|
||||||
|
ESP_LOGD(TAG, "Refresh");
|
||||||
|
this->command(0x12);
|
||||||
|
this->data(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EPaperSpectraE6::deep_sleep() {
|
||||||
|
ESP_LOGD(TAG, "Deep sleep");
|
||||||
|
this->command(0x07);
|
||||||
|
this->data(0xA5);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperSpectraE6::fill(Color color) {
|
void EPaperSpectraE6::fill(Color color) {
|
||||||
uint8_t pixel_color;
|
auto pixel_color = color_to_hex(color);
|
||||||
if (color.is_on()) {
|
|
||||||
pixel_color = color_to_hex(color);
|
|
||||||
} else {
|
|
||||||
pixel_color = 0x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We store 8 bitset<3> in 3 bytes
|
// We store 2 pixels per byte
|
||||||
// | byte 1 | byte 2 | byte 3 |
|
this->buffer_.fill(pixel_color + (pixel_color << 4));
|
||||||
// |aaabbbaa|abbbaaab|bbaaabbb|
|
|
||||||
uint8_t byte_1 = pixel_color << 5 | pixel_color << 2 | pixel_color >> 1;
|
|
||||||
uint8_t byte_2 = pixel_color << 7 | pixel_color << 4 | pixel_color << 1 | pixel_color >> 2;
|
|
||||||
uint8_t byte_3 = pixel_color << 6 | pixel_color << 3 | pixel_color << 0;
|
|
||||||
|
|
||||||
const size_t buffer_length = this->get_buffer_length();
|
|
||||||
for (size_t i = 0; i < buffer_length; i += 3) {
|
|
||||||
this->buffer_[i + 0] = byte_1;
|
|
||||||
this->buffer_[i + 1] = byte_2;
|
|
||||||
this->buffer_[i + 2] = byte_3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t EPaperSpectraE6::get_buffer_length() {
|
void EPaperSpectraE6::clear() {
|
||||||
// 6 colors buffer, 1 pixel = 3 bits, we will store 8 pixels in 24 bits = 3 bytes
|
// clear buffer to white, just like real paper.
|
||||||
return this->get_width_controller() * this->get_height_internal() / 8u * 3u;
|
this->fill(COLOR_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
void HOT EPaperSpectraE6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||||
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint8_t pixel_bits = color_to_hex(color);
|
auto pixel_bits = color_to_hex(color);
|
||||||
uint32_t pixel_position = x + y * this->get_width_controller();
|
uint32_t pixel_position = x + y * this->get_width_controller();
|
||||||
uint32_t first_bit_position = pixel_position * 3;
|
uint32_t byte_position = pixel_position / 2;
|
||||||
uint32_t byte_position = first_bit_position / 8u;
|
auto original = this->buffer_[byte_position];
|
||||||
uint32_t byte_subposition = first_bit_position % 8u;
|
if ((pixel_position & 1) != 0) {
|
||||||
|
this->buffer_[byte_position] = (original & 0xF0) | pixel_bits;
|
||||||
if (byte_subposition <= 5) {
|
|
||||||
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 << (5 - byte_subposition)))) |
|
|
||||||
(pixel_bits << (5 - byte_subposition));
|
|
||||||
} else {
|
} else {
|
||||||
this->buffer_[byte_position] = (this->buffer_[byte_position] & (0xFF ^ (0b111 >> (byte_subposition - 5)))) |
|
this->buffer_[byte_position] = (original & 0x0F) | (pixel_bits << 4);
|
||||||
(pixel_bits >> (byte_subposition - 5));
|
|
||||||
|
|
||||||
this->buffer_[byte_position + 1] =
|
|
||||||
(this->buffer_[byte_position + 1] & (0xFF ^ (0xFF & (0b111 << (13 - byte_subposition))))) |
|
|
||||||
(pixel_bits << (13 - byte_subposition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HOT EPaperSpectraE6::transfer_data() {
|
bool HOT EPaperSpectraE6::transfer_data() {
|
||||||
const uint32_t start_time = App.get_loop_component_start_time();
|
const uint32_t start_time = App.get_loop_component_start_time();
|
||||||
|
const size_t buffer_length = this->buffer_length_;
|
||||||
if (this->current_data_index_ == 0) {
|
if (this->current_data_index_ == 0) {
|
||||||
ESP_LOGV(TAG, "Sending data");
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
|
this->transfer_start_time_ = millis();
|
||||||
|
#endif
|
||||||
|
ESP_LOGV(TAG, "Start sending data at %ums", (unsigned) millis());
|
||||||
this->command(0x10);
|
this->command(0x10);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t bytes_to_send[4]{0};
|
size_t buf_idx = 0;
|
||||||
const size_t buffer_length = this->get_buffer_length();
|
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||||
for (size_t i = this->current_data_index_; i < buffer_length; i += 3) {
|
while (this->current_data_index_ != buffer_length) {
|
||||||
const uint32_t triplet = encode_uint24(this->buffer_[i + 0], this->buffer_[i + 1], this->buffer_[i + 2]);
|
bytes_to_send[buf_idx++] = this->buffer_[this->current_data_index_++];
|
||||||
// 8 pixels are stored in 3 bytes
|
|
||||||
// |aaabbbaa|abbbaaab|bbaaabbb|
|
|
||||||
// | byte 1 | byte 2 | byte 3 |
|
|
||||||
bytes_to_send[0] = ((triplet >> 17) & 0b01110000) | ((triplet >> 18) & 0b00000111);
|
|
||||||
bytes_to_send[1] = ((triplet >> 11) & 0b01110000) | ((triplet >> 12) & 0b00000111);
|
|
||||||
bytes_to_send[2] = ((triplet >> 5) & 0b01110000) | ((triplet >> 6) & 0b00000111);
|
|
||||||
bytes_to_send[3] = ((triplet << 1) & 0b01110000) | ((triplet << 0) & 0b00000111);
|
|
||||||
|
|
||||||
this->start_data_();
|
if (buf_idx == sizeof bytes_to_send) {
|
||||||
this->write_array(bytes_to_send, sizeof(bytes_to_send));
|
this->start_data_();
|
||||||
this->end_data_();
|
this->write_array(bytes_to_send, buf_idx);
|
||||||
|
this->end_data_();
|
||||||
|
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
|
||||||
|
buf_idx = 0;
|
||||||
|
|
||||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||||
// Let the main loop run and come back next loop
|
// Let the main loop run and come back next loop
|
||||||
this->current_data_index_ = i + 3;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finished the entire dataset
|
// Finished the entire dataset
|
||||||
|
if (buf_idx != 0) {
|
||||||
|
this->start_data_();
|
||||||
|
this->write_array(bytes_to_send, buf_idx);
|
||||||
|
this->end_data_();
|
||||||
|
}
|
||||||
this->current_data_index_ = 0;
|
this->current_data_index_ = 0;
|
||||||
|
ESP_LOGV(TAG, "Sent data in %" PRIu32 " ms", millis() - this->transfer_start_time_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperSpectraE6::reset() {
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
|
||||||
this->disable_loop();
|
|
||||||
this->reset_pin_->digital_write(true);
|
|
||||||
this->set_timeout(20, [this] {
|
|
||||||
this->reset_pin_->digital_write(false);
|
|
||||||
delay(2);
|
|
||||||
this->reset_pin_->digital_write(true);
|
|
||||||
this->set_timeout(20, [this] { this->enable_loop(); });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
} // namespace esphome::epaper_spi
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ namespace esphome::epaper_spi {
|
|||||||
|
|
||||||
class EPaperSpectraE6 : public EPaperBase {
|
class EPaperSpectraE6 : public EPaperBase {
|
||||||
public:
|
public:
|
||||||
EPaperSpectraE6(const uint8_t *init_sequence, const size_t init_sequence_length)
|
EPaperSpectraE6(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||||
: EPaperBase(init_sequence, init_sequence_length) {}
|
size_t init_sequence_length)
|
||||||
|
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_COLOR) {
|
||||||
|
this->buffer_length_ = width * height / 2; // 2 pixels per byte
|
||||||
|
}
|
||||||
|
|
||||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
|
||||||
void fill(Color color) override;
|
void fill(Color color) override;
|
||||||
|
void clear() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void refresh_screen() override;
|
||||||
|
void power_on() override;
|
||||||
|
void power_off() override;
|
||||||
|
void deep_sleep() override;
|
||||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
uint32_t get_buffer_length() override;
|
|
||||||
|
|
||||||
bool transfer_data() override;
|
bool transfer_data() override;
|
||||||
void reset() override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::epaper_spi
|
} // namespace esphome::epaper_spi
|
||||||
|
|||||||
65
esphome/components/epaper_spi/models/__init__.py
Normal file
65
esphome/components/epaper_spi/models/__init__.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from typing import Any, Self
|
||||||
|
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_WIDTH
|
||||||
|
|
||||||
|
|
||||||
|
class EpaperModel:
|
||||||
|
models: dict[str, Self] = {}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
class_name: str,
|
||||||
|
initsequence=None,
|
||||||
|
**defaults,
|
||||||
|
):
|
||||||
|
name = name.upper()
|
||||||
|
self.name = name
|
||||||
|
self.class_name = class_name
|
||||||
|
self.initsequence = initsequence
|
||||||
|
self.defaults = defaults
|
||||||
|
EpaperModel.models[name] = self
|
||||||
|
|
||||||
|
def get_default(self, key, fallback: Any = False) -> Any:
|
||||||
|
return self.defaults.get(key, fallback)
|
||||||
|
|
||||||
|
def get_init_sequence(self, config: dict):
|
||||||
|
return self.initsequence
|
||||||
|
|
||||||
|
def option(self, name, fallback=cv.UNDEFINED) -> cv.Optional | cv.Required:
|
||||||
|
if fallback is None and self.get_default(name, None) is None:
|
||||||
|
return cv.Required(name)
|
||||||
|
return cv.Optional(name, default=self.get_default(name, fallback))
|
||||||
|
|
||||||
|
def get_dimensions(self, config) -> tuple[int, int]:
|
||||||
|
if CONF_DIMENSIONS in config:
|
||||||
|
# Explicit dimensions, just use as is
|
||||||
|
dimensions = config[CONF_DIMENSIONS]
|
||||||
|
if isinstance(dimensions, dict):
|
||||||
|
width = dimensions[CONF_WIDTH]
|
||||||
|
height = dimensions[CONF_HEIGHT]
|
||||||
|
else:
|
||||||
|
(width, height) = dimensions
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Default dimensions, use model defaults
|
||||||
|
width = self.get_default(CONF_WIDTH)
|
||||||
|
height = self.get_default(CONF_HEIGHT)
|
||||||
|
return width, height
|
||||||
|
|
||||||
|
def extend(self, name, **kwargs) -> "EpaperModel":
|
||||||
|
"""
|
||||||
|
Extend the current model with additional parameters or a modified init sequence.
|
||||||
|
Parameters supplied here will override the defaults of the current model.
|
||||||
|
if the initsequence is not provided, the current model's initsequence will be used.
|
||||||
|
If add_init_sequence is provided, it will be appended to the current initsequence.
|
||||||
|
:param name:
|
||||||
|
:param kwargs:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
initsequence = list(kwargs.pop("initsequence", self.initsequence) or ())
|
||||||
|
initsequence.extend(kwargs.pop("add_init_sequence", ()))
|
||||||
|
defaults = self.defaults.copy()
|
||||||
|
defaults.update(kwargs)
|
||||||
|
return self.__class__(name, initsequence=tuple(initsequence), **defaults)
|
||||||
51
esphome/components/epaper_spi/models/spectra_e6.py
Normal file
51
esphome/components/epaper_spi/models/spectra_e6.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from . import EpaperModel
|
||||||
|
|
||||||
|
|
||||||
|
class SpectraE6(EpaperModel):
|
||||||
|
def __init__(self, name, class_name="EPaperSpectraE6", **kwargs):
|
||||||
|
super().__init__(name, class_name, **kwargs)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
def get_init_sequence(self, config: dict):
|
||||||
|
width, height = self.get_dimensions(config)
|
||||||
|
return (
|
||||||
|
(0xAA, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18,),
|
||||||
|
(0x01, 0x3F,),
|
||||||
|
(0x00, 0x5F, 0x69,),
|
||||||
|
(0x03, 0x00, 0x54, 0x00, 0x44,),
|
||||||
|
(0x05, 0x40, 0x1F, 0x1F, 0x2C,),
|
||||||
|
(0x06, 0x6F, 0x1F, 0x17, 0x49,),
|
||||||
|
(0x08, 0x6F, 0x1F, 0x1F, 0x22,),
|
||||||
|
(0x30, 0x03,),
|
||||||
|
(0x50, 0x3F,),
|
||||||
|
(0x60, 0x02, 0x00,),
|
||||||
|
(0x61, width // 256, width % 256, height // 256, height % 256,),
|
||||||
|
(0x84, 0x01,),
|
||||||
|
(0xE3, 0x2F,),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_default(self, key, fallback: Any = False) -> Any:
|
||||||
|
return self.defaults.get(key, fallback)
|
||||||
|
|
||||||
|
|
||||||
|
spectra_e6 = SpectraE6("spectra-e6")
|
||||||
|
|
||||||
|
spectra_e6.extend(
|
||||||
|
"Seeed-reTerminal-E1002",
|
||||||
|
width=800,
|
||||||
|
height=480,
|
||||||
|
data_rate="20MHz",
|
||||||
|
cs_pin=10,
|
||||||
|
dc_pin=11,
|
||||||
|
reset_pin=12,
|
||||||
|
busy_pin={
|
||||||
|
"number": 13,
|
||||||
|
"inverted": True,
|
||||||
|
"mode": {
|
||||||
|
"input": True,
|
||||||
|
"pullup": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace es8388 {
|
namespace es8388 {
|
||||||
|
|
||||||
void ADCInputMicSelect::control(const std::string &value) {
|
void ADCInputMicSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(this->index_of(value).value()));
|
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
|||||||
|
|
||||||
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
|
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace es8388 {
|
namespace es8388 {
|
||||||
|
|
||||||
void DacOutputSelect::control(const std::string &value) {
|
void DacOutputSelect::control(size_t index) {
|
||||||
this->publish_state(value);
|
this->publish_state(index);
|
||||||
this->parent_->set_dac_output(static_cast<DacOutputLine>(this->index_of(value).value()));
|
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace es8388 {
|
|||||||
|
|
||||||
class DacOutputSelect : public select::Select, public Parented<ES8388> {
|
class DacOutputSelect : public select::Select, public Parented<ES8388> {
|
||||||
protected:
|
protected:
|
||||||
void control(const std::string &value) override;
|
void control(size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace es8388
|
} // namespace es8388
|
||||||
|
|||||||
@@ -558,6 +558,7 @@ CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram"
|
|||||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios"
|
CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios"
|
||||||
CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
|
CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
|
||||||
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
|
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
|
||||||
|
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
||||||
|
|
||||||
# VFS requirement tracking
|
# VFS requirement tracking
|
||||||
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
|
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
|
||||||
@@ -654,6 +655,9 @@ FRAMEWORK_SCHEMA = cv.All(
|
|||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||||
|
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||||
|
min=8192, max=32768
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||||
@@ -926,6 +930,10 @@ async def to_code(config):
|
|||||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_ARDUINO_LOOP_STACK_SIZE",
|
||||||
|
conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE],
|
||||||
|
)
|
||||||
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
|
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||||
@@ -1071,6 +1079,10 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
|
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
|
||||||
|
|
||||||
|
cg.add_define(
|
||||||
|
"ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE)
|
||||||
|
)
|
||||||
|
|
||||||
cg.add_define(
|
cg.add_define(
|
||||||
"USE_ESP_IDF_VERSION_CODE",
|
"USE_ESP_IDF_VERSION_CODE",
|
||||||
cg.RawExpression(
|
cg.RawExpression(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
@@ -96,7 +97,11 @@ void loop_task(void *pv_params) {
|
|||||||
|
|
||||||
extern "C" void app_main() {
|
extern "C" void app_main() {
|
||||||
esp32::setup_preferences();
|
esp32::setup_preferences();
|
||||||
xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle);
|
#if CONFIG_FREERTOS_UNICORE
|
||||||
|
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
|
||||||
|
#else
|
||||||
|
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif // USE_ESP_IDF
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import socket
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -21,6 +22,7 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority
|
|||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
AUTO_LOAD = ["socket"]
|
||||||
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
|
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
|
||||||
DOMAIN = "esp32_ble"
|
DOMAIN = "esp32_ble"
|
||||||
|
|
||||||
@@ -481,6 +483,11 @@ async def to_code(config):
|
|||||||
cg.add(var.set_name(name))
|
cg.add(var.set_name(name))
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
# BLE uses the socket wake_loop_threadsafe() mechanism to wake the main loop from BLE tasks
|
||||||
|
# This enables low-latency (~12μs) BLE event processing instead of waiting for
|
||||||
|
# select() timeout (0-16ms). The wake socket is shared across all components.
|
||||||
|
socket.require_wake_loop_threadsafe()
|
||||||
|
|
||||||
# Define max connections for use in C++ code (e.g., ble_server.h)
|
# Define max connections for use in C++ code (e.g., ble_server.h)
|
||||||
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
|
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
|
||||||
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
|
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
|
||||||
|
|||||||
@@ -31,6 +31,26 @@ namespace esphome::esp32_ble {
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_ble";
|
static const char *const TAG = "esp32_ble";
|
||||||
|
|
||||||
|
// GAP event groups for deduplication across gap_event_handler and dispatch_gap_event_
|
||||||
|
#define GAP_SCAN_COMPLETE_EVENTS \
|
||||||
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
|
||||||
|
|
||||||
|
#define GAP_ADV_COMPLETE_EVENTS \
|
||||||
|
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: \
|
||||||
|
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
|
||||||
|
|
||||||
|
#define GAP_SECURITY_EVENTS \
|
||||||
|
case ESP_GAP_BLE_AUTH_CMPL_EVT: \
|
||||||
|
case ESP_GAP_BLE_SEC_REQ_EVT: \
|
||||||
|
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: \
|
||||||
|
case ESP_GAP_BLE_PASSKEY_REQ_EVT: \
|
||||||
|
case ESP_GAP_BLE_NC_REQ_EVT
|
||||||
|
|
||||||
void ESP32BLE::setup() {
|
void ESP32BLE::setup() {
|
||||||
global_ble = this;
|
global_ble = this;
|
||||||
if (!ble_pre_setup_()) {
|
if (!ble_pre_setup_()) {
|
||||||
@@ -414,60 +434,48 @@ void ESP32BLE::loop() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// Scan complete events
|
// Scan complete events
|
||||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
GAP_SCAN_COMPLETE_EVENTS:
|
||||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
|
||||||
// All three scan complete events have the same structure with just status
|
|
||||||
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
|
||||||
// This is verified at compile-time by static_assert checks in ble_event.h
|
|
||||||
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
|
||||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
|
||||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
|
||||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
|
||||||
gap_handler->gap_event_handler(
|
|
||||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Advertising complete events
|
// Advertising complete events
|
||||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
GAP_ADV_COMPLETE_EVENTS:
|
||||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
|
||||||
// All advertising complete events have the same structure with just status
|
|
||||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
|
||||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
|
||||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
|
||||||
gap_handler->gap_event_handler(
|
|
||||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
// RSSI complete event
|
// RSSI complete event
|
||||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
|
||||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
|
||||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
|
||||||
gap_handler->gap_event_handler(
|
|
||||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Security events
|
// Security events
|
||||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
GAP_SECURITY_EVENTS:
|
||||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
|
||||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
|
||||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
|
||||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
|
||||||
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
ESP_LOGV(TAG, "gap_event_handler - %d", gap_event);
|
||||||
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
|
||||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
{
|
||||||
gap_handler->gap_event_handler(
|
esp_ble_gap_cb_param_t *param;
|
||||||
gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security));
|
// clang-format off
|
||||||
|
switch (gap_event) {
|
||||||
|
// All three scan complete events have the same structure with just status
|
||||||
|
// The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe
|
||||||
|
// This is verified at compile-time by static_assert checks in ble_event.h
|
||||||
|
// The struct already contains our copy of the status (copied in BLEEvent constructor)
|
||||||
|
GAP_SCAN_COMPLETE_EVENTS:
|
||||||
|
param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// All advertising complete events have the same structure with just status
|
||||||
|
GAP_ADV_COMPLETE_EVENTS:
|
||||||
|
param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||||
|
param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete);
|
||||||
|
break;
|
||||||
|
|
||||||
|
GAP_SECURITY_EVENTS:
|
||||||
|
param = reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
// Dispatch to all registered handlers
|
||||||
|
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||||
|
gap_handler->gap_event_handler(gap_event, param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
@@ -547,26 +555,24 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa
|
|||||||
// Queue GAP events that components need to handle
|
// Queue GAP events that components need to handle
|
||||||
// Scanning events - used by esp32_ble_tracker
|
// Scanning events - used by esp32_ble_tracker
|
||||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
GAP_SCAN_COMPLETE_EVENTS:
|
||||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
|
||||||
// Advertising events - used by esp32_ble_beacon and esp32_ble server
|
// Advertising events - used by esp32_ble_beacon and esp32_ble server
|
||||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
GAP_ADV_COMPLETE_EVENTS:
|
||||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
|
||||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
|
||||||
// Connection events - used by ble_client
|
// Connection events - used by ble_client
|
||||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||||
// Security events - used by ble_client and bluetooth_proxy
|
|
||||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
|
||||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
|
||||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
|
||||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
|
||||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
|
||||||
enqueue_ble_event(event, param);
|
enqueue_ble_event(event, param);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Security events - used by ble_client and bluetooth_proxy
|
||||||
|
// These are rare but interactive (pairing/bonding), so notify immediately
|
||||||
|
GAP_SECURITY_EVENTS:
|
||||||
|
enqueue_ble_event(event, param);
|
||||||
|
// Wake up main loop to process security event immediately
|
||||||
|
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||||
|
App.wake_loop_threadsafe();
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
|
||||||
// Ignore these GAP events as they are not relevant for our use case
|
// Ignore these GAP events as they are not relevant for our use case
|
||||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||||
case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
|
case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT:
|
||||||
@@ -584,6 +590,10 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa
|
|||||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||||
esp_ble_gatts_cb_param_t *param) {
|
esp_ble_gatts_cb_param_t *param) {
|
||||||
enqueue_ble_event(event, gatts_if, param);
|
enqueue_ble_event(event, gatts_if, param);
|
||||||
|
// Wake up main loop to process GATT event immediately
|
||||||
|
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||||
|
App.wake_loop_threadsafe();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -591,6 +601,10 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat
|
|||||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
enqueue_ble_event(event, gattc_if, param);
|
enqueue_ble_event(event, gattc_if, param);
|
||||||
|
// Wake up main loop to process GATT event immediately
|
||||||
|
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||||
|
App.wake_loop_threadsafe();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ class ESP32BLE : public Component {
|
|||||||
void advertising_init_();
|
void advertising_init_();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// BLE uses the core wake_loop_threadsafe() mechanism to wake the main event loop
|
||||||
|
// from BLE tasks. This enables low-latency (~12μs) event processing instead of
|
||||||
|
// waiting for select() timeout (0-16ms). The wake socket is shared with other
|
||||||
|
// components that need this functionality.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
||||||
|
|
||||||
@@ -209,17 +214,17 @@ extern ESP32BLE *global_ble;
|
|||||||
|
|
||||||
template<typename... Ts> class BLEEnabledCondition : public Condition<Ts...> {
|
template<typename... Ts> class BLEEnabledCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
bool check(Ts... x) override { return global_ble->is_active(); }
|
bool check(const Ts &...x) override { return global_ble->is_active(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class BLEEnableAction : public Action<Ts...> {
|
template<typename... Ts> class BLEEnableAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { global_ble->enable(); }
|
void play(const Ts &...x) override { global_ble->enable(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class BLEDisableAction : public Action<Ts...> {
|
template<typename... Ts> class BLEDisableAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { global_ble->disable(); }
|
void play(const Ts &...x) override { global_ble->disable(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble
|
} // namespace esphome::esp32_ble
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T
|
|||||||
BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
||||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
// If the listener is already set, do nothing
|
// If the listener is already set, do nothing
|
||||||
if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_))
|
if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_))
|
||||||
return;
|
return;
|
||||||
@@ -96,7 +96,7 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T
|
|||||||
template<typename... Ts> class BLECharacteristicNotifyAction : public Action<Ts...> {
|
template<typename... Ts> class BLECharacteristicNotifyAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
BLECharacteristicNotifyAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
BLECharacteristicNotifyAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
#ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION
|
#ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION
|
||||||
// Call the pre-notify event
|
// Call the pre-notify event
|
||||||
BLECharacteristicSetValueActionManager::get_instance()->emit_pre_notify(this->parent_);
|
BLECharacteristicSetValueActionManager::get_instance()->emit_pre_notify(this->parent_);
|
||||||
@@ -116,7 +116,7 @@ template<typename... Ts> class BLEDescriptorSetValueAction : public Action<Ts...
|
|||||||
BLEDescriptorSetValueAction(BLEDescriptor *descriptor) : parent_(descriptor) {}
|
BLEDescriptorSetValueAction(BLEDescriptor *descriptor) : parent_(descriptor) {}
|
||||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||||
void play(Ts... x) override { this->parent_->set_value(this->buffer_.value(x...)); }
|
void play(const Ts &...x) override { this->parent_->set_value(this->buffer_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BLEDescriptor *parent_;
|
BLEDescriptor *parent_;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {}
|
ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(bool, continuous)
|
TEMPLATABLE_VALUE(bool, continuous)
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
this->parent_->set_scan_continuous(this->continuous_.value(x...));
|
this->parent_->set_scan_continuous(this->continuous_.value(x...));
|
||||||
this->parent_->start_scan();
|
this->parent_->start_scan();
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
|||||||
|
|
||||||
template<typename... Ts> class ESP32BLEStopScanAction : public Action<Ts...>, public Parented<ESP32BLETracker> {
|
template<typename... Ts> class ESP32BLEStopScanAction : public Action<Ts...>, public Parented<ESP32BLETracker> {
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override { this->parent_->stop_scan(); }
|
void play(const Ts &...x) override { this->parent_->stop_scan(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_tracker
|
} // namespace esphome::esp32_ble_tracker
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from esphome import automation, pins
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.components.esp32 import add_idf_component
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
from esphome.components.psram import DOMAIN as psram_domain
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BRIGHTNESS,
|
CONF_BRIGHTNESS,
|
||||||
@@ -26,10 +27,9 @@ import esphome.final_validate as fv
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
AUTO_LOAD = ["camera"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
||||||
AUTO_LOAD = ["camera", "psram"]
|
|
||||||
|
|
||||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||||
ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData")
|
ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData")
|
||||||
@@ -163,6 +163,14 @@ CONF_ON_IMAGE = "on_image"
|
|||||||
|
|
||||||
camera_range_param = cv.int_range(min=-2, max=2)
|
camera_range_param = cv.int_range(min=-2, max=2)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_fb_location_(value):
|
||||||
|
validator = cv.enum(ENUM_FB_LOCATION, upper=True)
|
||||||
|
if value.lower() == psram_domain:
|
||||||
|
validator = cv.All(validator, cv.requires_component(psram_domain))
|
||||||
|
return validator(value)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.ENTITY_BASE_SCHEMA.extend(
|
cv.ENTITY_BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@@ -236,9 +244,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.framerate, cv.Range(min=0, max=1)
|
cv.framerate, cv.Range(min=0, max=1)
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2),
|
cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2),
|
||||||
cv.Optional(CONF_FRAME_BUFFER_LOCATION, default="PSRAM"): cv.enum(
|
cv.Optional(
|
||||||
ENUM_FB_LOCATION, upper=True
|
CONF_FRAME_BUFFER_LOCATION, default="PSRAM"
|
||||||
),
|
): validate_fb_location_,
|
||||||
cv.Optional(CONF_ON_STREAM_START): automation.validate_automation(
|
cv.Optional(CONF_ON_STREAM_START): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
|||||||
78
esphome/components/esp32_hosted/update/__init__.py
Normal file
78
esphome/components/esp32_hosted/update/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import hashlib
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import esp32, update
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_PATH, CONF_RAW_DATA_ID
|
||||||
|
from esphome.core import CORE, HexInt
|
||||||
|
|
||||||
|
CODEOWNERS = ["@swoboda1337"]
|
||||||
|
AUTO_LOAD = ["sha256", "watchdog"]
|
||||||
|
DEPENDENCIES = ["esp32_hosted"]
|
||||||
|
|
||||||
|
CONF_SHA256 = "sha256"
|
||||||
|
|
||||||
|
esp32_hosted_ns = cg.esphome_ns.namespace("esp32_hosted")
|
||||||
|
Esp32HostedUpdate = esp32_hosted_ns.class_(
|
||||||
|
"Esp32HostedUpdate", update.UpdateEntity, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_sha256(value: Any) -> str:
|
||||||
|
value = cv.string_strict(value)
|
||||||
|
if len(value) != 64:
|
||||||
|
raise cv.Invalid("SHA256 must be 64 hexadecimal characters")
|
||||||
|
try:
|
||||||
|
bytes.fromhex(value)
|
||||||
|
except ValueError as e:
|
||||||
|
raise cv.Invalid(f"SHA256 must be valid hexadecimal: {e}") from e
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
update.update_schema(Esp32HostedUpdate, device_class="firmware").extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||||
|
cv.Required(CONF_PATH): cv.file_,
|
||||||
|
cv.Required(CONF_SHA256): _validate_sha256,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
esp32.only_on_variant(
|
||||||
|
supported=[
|
||||||
|
esp32.const.VARIANT_ESP32H2,
|
||||||
|
esp32.const.VARIANT_ESP32P4,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_firmware(config: dict[str, Any]) -> None:
|
||||||
|
path = CORE.relative_config_path(config[CONF_PATH])
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
firmware_data = f.read()
|
||||||
|
calculated = hashlib.sha256(firmware_data).hexdigest()
|
||||||
|
expected = config[CONF_SHA256].lower()
|
||||||
|
if calculated != expected:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"SHA256 mismatch for {config[CONF_PATH]}: expected {expected}, got {calculated}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _validate_firmware
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config: dict[str, Any]) -> None:
|
||||||
|
var = await update.new_update(config)
|
||||||
|
|
||||||
|
path = config[CONF_PATH]
|
||||||
|
with open(CORE.relative_config_path(path), "rb") as f:
|
||||||
|
firmware_data = f.read()
|
||||||
|
rhs = [HexInt(x) for x in firmware_data]
|
||||||
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||||
|
|
||||||
|
sha256_bytes = bytes.fromhex(config[CONF_SHA256])
|
||||||
|
cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes]))
|
||||||
|
cg.add(var.set_firmware_data(prog_arr))
|
||||||
|
cg.add(var.set_firmware_size(len(firmware_data)))
|
||||||
|
await cg.register_component(var, config)
|
||||||
164
esphome/components/esp32_hosted/update/esp32_hosted_update.cpp
Normal file
164
esphome/components/esp32_hosted/update/esp32_hosted_update.cpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#if defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
|
#include "esp32_hosted_update.h"
|
||||||
|
#include "esphome/components/watchdog/watchdog.h"
|
||||||
|
#include "esphome/components/sha256/sha256.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include <esp_image_format.h>
|
||||||
|
#include <esp_app_desc.h>
|
||||||
|
#include <esp_hosted.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <esp_hosted_ota.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace esphome::esp32_hosted {
|
||||||
|
|
||||||
|
static const char *const TAG = "esp32_hosted.update";
|
||||||
|
|
||||||
|
// older coprocessor firmware versions have a 1500-byte limit per RPC call
|
||||||
|
constexpr size_t CHUNK_SIZE = 1500;
|
||||||
|
|
||||||
|
void Esp32HostedUpdate::setup() {
|
||||||
|
this->update_info_.title = "ESP32 Hosted Coprocessor";
|
||||||
|
|
||||||
|
// get coprocessor version
|
||||||
|
esp_hosted_coprocessor_fwver_t ver_info;
|
||||||
|
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||||
|
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||||
|
} else {
|
||||||
|
this->update_info_.current_version = "unknown";
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Coprocessor version: %s", this->update_info_.current_version.c_str());
|
||||||
|
|
||||||
|
// get image version
|
||||||
|
const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t);
|
||||||
|
if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) {
|
||||||
|
esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset);
|
||||||
|
if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) {
|
||||||
|
ESP_LOGD(TAG, "Firmware version: %s", app_desc->version);
|
||||||
|
ESP_LOGD(TAG, "Project name: %s", app_desc->project_name);
|
||||||
|
ESP_LOGD(TAG, "Build date: %s", app_desc->date);
|
||||||
|
ESP_LOGD(TAG, "Build time: %s", app_desc->time);
|
||||||
|
ESP_LOGD(TAG, "IDF version: %s", app_desc->idf_ver);
|
||||||
|
this->update_info_.latest_version = app_desc->version;
|
||||||
|
if (this->update_info_.latest_version != this->update_info_.current_version) {
|
||||||
|
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||||
|
} else {
|
||||||
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Invalid app description magic word: 0x%08x (expected 0x%08x)", app_desc->magic_word,
|
||||||
|
ESP_APP_DESC_MAGIC_WORD);
|
||||||
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Firmware too small to contain app description");
|
||||||
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish state
|
||||||
|
this->status_clear_error();
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Esp32HostedUpdate::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"ESP32 Hosted Update:\n"
|
||||||
|
" Current Version: %s\n"
|
||||||
|
" Latest Version: %s\n"
|
||||||
|
" Latest Size: %zu bytes",
|
||||||
|
this->update_info_.current_version.c_str(), this->update_info_.latest_version.c_str(),
|
||||||
|
this->firmware_size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Esp32HostedUpdate::perform(bool force) {
|
||||||
|
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||||
|
ESP_LOGW(TAG, "Update not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->firmware_data_ == nullptr || this->firmware_size_ == 0) {
|
||||||
|
ESP_LOGE(TAG, "No firmware data available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256::SHA256 hasher;
|
||||||
|
hasher.init();
|
||||||
|
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||||
|
hasher.calculate();
|
||||||
|
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||||
|
this->status_set_error("SHA256 verification failed");
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_);
|
||||||
|
|
||||||
|
watchdog::WatchdogManager watchdog(20000);
|
||||||
|
update::UpdateState prev_state = this->state_;
|
||||||
|
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||||
|
this->update_info_.has_progress = false;
|
||||||
|
this->publish_state();
|
||||||
|
|
||||||
|
esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||||
|
this->state_ = prev_state;
|
||||||
|
this->status_set_error("Failed to begin OTA");
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t chunk[CHUNK_SIZE];
|
||||||
|
const uint8_t *data_ptr = this->firmware_data_;
|
||||||
|
size_t remaining = this->firmware_size_;
|
||||||
|
while (remaining > 0) {
|
||||||
|
size_t chunk_size = std::min(remaining, static_cast<size_t>(CHUNK_SIZE));
|
||||||
|
memcpy(chunk, data_ptr, chunk_size);
|
||||||
|
err = esp_hosted_slave_ota_write(chunk, chunk_size); // NOLINT
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||||
|
esp_hosted_slave_ota_end(); // NOLINT
|
||||||
|
this->state_ = prev_state;
|
||||||
|
this->status_set_error("Failed to write OTA data");
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data_ptr += chunk_size;
|
||||||
|
remaining -= chunk_size;
|
||||||
|
App.feed_wdt();
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_hosted_slave_ota_end(); // NOLINT
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
|
||||||
|
this->state_ = prev_state;
|
||||||
|
this->status_set_error("Failed to end OTA");
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate new firmware
|
||||||
|
err = esp_hosted_slave_ota_activate(); // NOLINT
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
|
||||||
|
this->state_ = prev_state;
|
||||||
|
this->status_set_error("Failed to activate OTA");
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
ESP_LOGI(TAG, "OTA update successful");
|
||||||
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
this->status_clear_error();
|
||||||
|
this->publish_state();
|
||||||
|
|
||||||
|
// schedule a restart to ensure everything is in sync
|
||||||
|
ESP_LOGI(TAG, "Restarting in 1 second");
|
||||||
|
this->set_timeout(1000, []() { App.safe_reboot(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::esp32_hosted
|
||||||
|
#endif
|
||||||
32
esphome/components/esp32_hosted/update/esp32_hosted_update.h
Normal file
32
esphome/components/esp32_hosted/update/esp32_hosted_update.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/update/update_entity.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace esphome::esp32_hosted {
|
||||||
|
|
||||||
|
class Esp32HostedUpdate : public update::UpdateEntity, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
void perform(bool force) override;
|
||||||
|
void check() override {}
|
||||||
|
|
||||||
|
void set_firmware_data(const uint8_t *data) { this->firmware_data_ = data; }
|
||||||
|
void set_firmware_size(size_t size) { this->firmware_size_ = size; }
|
||||||
|
void set_firmware_sha256(const std::array<uint8_t, 32> &sha256) { this->firmware_sha256_ = sha256; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const uint8_t *firmware_data_{nullptr};
|
||||||
|
size_t firmware_size_{0};
|
||||||
|
std::array<uint8_t, 32> firmware_sha256_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::esp32_hosted
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -40,7 +40,7 @@ template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
|||||||
SetFrequencyAction(ESP8266PWM *parent) : parent_(parent) {}
|
SetFrequencyAction(ESP8266PWM *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(float, frequency);
|
TEMPLATABLE_VALUE(float, frequency);
|
||||||
|
|
||||||
void play(Ts... x) {
|
void play(const Ts &...x) {
|
||||||
float freq = this->frequency_.value(x...);
|
float freq = this->frequency_.value(x...);
|
||||||
this->parent_->update_frequency(freq);
|
this->parent_->update_frequency(freq);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ template<typename... Ts> class AdjustAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(float, voltage)
|
TEMPLATABLE_VALUE(float, voltage)
|
||||||
|
|
||||||
void play(Ts... x) override { this->ldo_->adjust_voltage(this->voltage_.value(x...)); }
|
void play(const Ts &...x) override { this->ldo_->adjust_voltage(this->voltage_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EspLdo *ldo_;
|
EspLdo *ldo_;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ void ESPHomeOTAComponent::dump_config() {
|
|||||||
"Over-The-Air updates:\n"
|
"Over-The-Air updates:\n"
|
||||||
" Address: %s:%u\n"
|
" Address: %s:%u\n"
|
||||||
" Version: %d",
|
" Version: %d",
|
||||||
network::get_use_address().c_str(), this->port_, USE_OTA_VERSION);
|
network::get_use_address(), this->port_, USE_OTA_VERSION);
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
if (!this->password_.empty()) {
|
if (!this->password_.empty()) {
|
||||||
ESP_LOGCONFIG(TAG, " Password configured");
|
ESP_LOGCONFIG(TAG, " Password configured");
|
||||||
@@ -281,19 +281,15 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Acknowledge auth OK - 1 byte
|
// Acknowledge auth OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_AUTH_OK;
|
this->write_byte_(ota::OTA_RESPONSE_AUTH_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
// Read size, 4 bytes MSB first
|
// Read size, 4 bytes MSB first
|
||||||
if (!this->readall_(buf, 4)) {
|
if (!this->readall_(buf, 4)) {
|
||||||
this->log_read_error_(LOG_STR("size"));
|
this->log_read_error_(LOG_STR("size"));
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
ota_size = 0;
|
ota_size = (static_cast<size_t>(buf[0]) << 24) | (static_cast<size_t>(buf[1]) << 16) |
|
||||||
for (uint8_t i = 0; i < 4; i++) {
|
(static_cast<size_t>(buf[2]) << 8) | buf[3];
|
||||||
ota_size <<= 8;
|
|
||||||
ota_size |= buf[i];
|
|
||||||
}
|
|
||||||
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
||||||
|
|
||||||
// Now that we've passed authentication and are actually
|
// Now that we've passed authentication and are actually
|
||||||
@@ -313,8 +309,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
update_started = true;
|
update_started = true;
|
||||||
|
|
||||||
// Acknowledge prepare OK - 1 byte
|
// Acknowledge prepare OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_UPDATE_PREPARE_OK;
|
this->write_byte_(ota::OTA_RESPONSE_UPDATE_PREPARE_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
// Read binary MD5, 32 bytes
|
// Read binary MD5, 32 bytes
|
||||||
if (!this->readall_(buf, 32)) {
|
if (!this->readall_(buf, 32)) {
|
||||||
@@ -326,8 +321,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
this->backend_->set_update_md5(sbuf);
|
this->backend_->set_update_md5(sbuf);
|
||||||
|
|
||||||
// Acknowledge MD5 OK - 1 byte
|
// Acknowledge MD5 OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK;
|
this->write_byte_(ota::OTA_RESPONSE_BIN_MD5_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
while (total < ota_size) {
|
while (total < ota_size) {
|
||||||
// TODO: timeout check
|
// TODO: timeout check
|
||||||
@@ -354,8 +348,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
total += read;
|
total += read;
|
||||||
#if USE_OTA_VERSION == 2
|
#if USE_OTA_VERSION == 2
|
||||||
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
|
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
|
||||||
buf[0] = ota::OTA_RESPONSE_CHUNK_OK;
|
this->write_byte_(ota::OTA_RESPONSE_CHUNK_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
size_acknowledged += OTA_BLOCK_SIZE;
|
size_acknowledged += OTA_BLOCK_SIZE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -374,8 +367,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Acknowledge receive OK - 1 byte
|
// Acknowledge receive OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_RECEIVE_OK;
|
this->write_byte_(ota::OTA_RESPONSE_RECEIVE_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
error_code = this->backend_->end();
|
error_code = this->backend_->end();
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
@@ -384,8 +376,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Acknowledge Update end OK - 1 byte
|
// Acknowledge Update end OK - 1 byte
|
||||||
buf[0] = ota::OTA_RESPONSE_UPDATE_END_OK;
|
this->write_byte_(ota::OTA_RESPONSE_UPDATE_END_OK);
|
||||||
this->writeall_(buf, 1);
|
|
||||||
|
|
||||||
// Read ACK
|
// Read ACK
|
||||||
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
||||||
@@ -404,8 +395,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
App.safe_reboot();
|
App.safe_reboot();
|
||||||
|
|
||||||
error:
|
error:
|
||||||
buf[0] = static_cast<uint8_t>(error_code);
|
this->write_byte_(static_cast<uint8_t>(error_code));
|
||||||
this->writeall_(buf, 1);
|
|
||||||
this->cleanup_connection_();
|
this->cleanup_connection_();
|
||||||
|
|
||||||
if (this->backend_ != nullptr && update_started) {
|
if (this->backend_ != nullptr && update_started) {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
bool readall_(uint8_t *buf, size_t len);
|
bool readall_(uint8_t *buf, size_t len);
|
||||||
bool writeall_(const uint8_t *buf, size_t len);
|
bool writeall_(const uint8_t *buf, size_t len);
|
||||||
|
inline bool write_byte_(uint8_t byte) { return this->writeall_(&byte, 1); }
|
||||||
|
|
||||||
bool try_read_(size_t to_read, const LogString *desc);
|
bool try_read_(size_t to_read, const LogString *desc);
|
||||||
bool try_write_(size_t to_write, const LogString *desc);
|
bool try_write_(size_t to_write, const LogString *desc);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from esphome import automation, core
|
from esphome import automation, core
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import wifi
|
from esphome.components import socket, wifi
|
||||||
from esphome.components.udp import CONF_ON_RECEIVE
|
from esphome.components.udp import CONF_ON_RECEIVE
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -17,6 +17,7 @@ from esphome.core import CORE, HexInt
|
|||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
AUTO_LOAD = ["socket"]
|
||||||
|
|
||||||
byte_vector = cg.std_vector.template(cg.uint8)
|
byte_vector = cg.std_vector.template(cg.uint8)
|
||||||
peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6)
|
peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6)
|
||||||
@@ -120,6 +121,10 @@ async def to_code(config):
|
|||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
cg.add_library("WiFi", None)
|
cg.add_library("WiFi", None)
|
||||||
|
|
||||||
|
# ESP-NOW uses wake_loop_threadsafe() to wake the main loop from ESP-NOW callbacks
|
||||||
|
# This enables low-latency event processing instead of waiting for select() timeout
|
||||||
|
socket.require_wake_loop_threadsafe()
|
||||||
|
|
||||||
cg.add_define("USE_ESPNOW")
|
cg.add_define("USE_ESPNOW")
|
||||||
if wifi_channel := config.get(CONF_CHANNEL):
|
if wifi_channel := config.get(CONF_CHANNEL):
|
||||||
cg.add(var.set_wifi_channel(wifi_channel))
|
cg.add(var.set_wifi_channel(wifi_channel))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
|||||||
void set_wait_for_sent(bool wait_for_sent) { this->flags_.wait_for_sent = wait_for_sent; }
|
void set_wait_for_sent(bool wait_for_sent) { this->flags_.wait_for_sent = wait_for_sent; }
|
||||||
void set_continue_on_error(bool continue_on_error) { this->flags_.continue_on_error = continue_on_error; }
|
void set_continue_on_error(bool continue_on_error) { this->flags_.continue_on_error = continue_on_error; }
|
||||||
|
|
||||||
void play_complex(Ts... x) override {
|
void play_complex(const Ts &...x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
send_callback_t send_callback = [this, x...](esp_err_t status) {
|
send_callback_t send_callback = [this, x...](esp_err_t status) {
|
||||||
if (status == ESP_OK) {
|
if (status == ESP_OK) {
|
||||||
@@ -67,7 +67,7 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(Ts... x) override { /* ignore - see play_complex */
|
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() override {
|
void stop() override {
|
||||||
@@ -90,7 +90,7 @@ template<typename... Ts> class AddPeerAction : public Action<Ts...>, public Pare
|
|||||||
TEMPLATABLE_VALUE(peer_address_t, address);
|
TEMPLATABLE_VALUE(peer_address_t, address);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
peer_address_t address = this->address_.value(x...);
|
peer_address_t address = this->address_.value(x...);
|
||||||
this->parent_->add_peer(address.data());
|
this->parent_->add_peer(address.data());
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ template<typename... Ts> class DeletePeerAction : public Action<Ts...>, public P
|
|||||||
TEMPLATABLE_VALUE(peer_address_t, address);
|
TEMPLATABLE_VALUE(peer_address_t, address);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
peer_address_t address = this->address_.value(x...);
|
peer_address_t address = this->address_.value(x...);
|
||||||
this->parent_->del_peer(address.data());
|
this->parent_->del_peer(address.data());
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ template<typename... Ts> class DeletePeerAction : public Action<Ts...>, public P
|
|||||||
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<ESPNowComponent> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
if (this->parent_->is_wifi_enabled()) {
|
if (this->parent_->is_wifi_enabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "espnow_err.h"
|
#include "espnow_err.h"
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@@ -97,6 +98,11 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
|
|||||||
// Push the packet to the queue
|
// Push the packet to the queue
|
||||||
global_esp_now->receive_packet_queue_.push(packet);
|
global_esp_now->receive_packet_queue_.push(packet);
|
||||||
// Push always because we're the only producer and the pool ensures we never exceed queue size
|
// Push always because we're the only producer and the pool ensures we never exceed queue size
|
||||||
|
|
||||||
|
// Wake main loop immediately to process ESP-NOW send event instead of waiting for select() timeout
|
||||||
|
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||||
|
App.wake_loop_threadsafe();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
|
||||||
@@ -114,6 +120,11 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int
|
|||||||
// Push the packet to the queue
|
// Push the packet to the queue
|
||||||
global_esp_now->receive_packet_queue_.push(packet);
|
global_esp_now->receive_packet_queue_.push(packet);
|
||||||
// Push always because we're the only producer and the pool ensures we never exceed queue size
|
// Push always because we're the only producer and the pool ensures we never exceed queue size
|
||||||
|
|
||||||
|
// Wake main loop immediately to process ESP-NOW receive event instead of waiting for select() timeout
|
||||||
|
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||||
|
App.wake_loop_threadsafe();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ESPNowComponent::ESPNowComponent() { global_esp_now = this; }
|
ESPNowComponent::ESPNowComponent() { global_esp_now = this; }
|
||||||
|
|||||||
@@ -691,9 +691,9 @@ void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_
|
|||||||
|
|
||||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||||
const std::string &EthernetComponent::get_use_address() const { return this->use_address_; }
|
const char *EthernetComponent::get_use_address() const { return this->use_address_; }
|
||||||
|
|
||||||
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
|
void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||||
|
|
||||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class EthernetComponent : public Component {
|
|||||||
|
|
||||||
network::IPAddresses get_ip_addresses();
|
network::IPAddresses get_ip_addresses();
|
||||||
network::IPAddress get_dns_address(uint8_t num);
|
network::IPAddress get_dns_address(uint8_t num);
|
||||||
const std::string &get_use_address() const;
|
const char *get_use_address() const;
|
||||||
void set_use_address(const std::string &use_address);
|
void set_use_address(const char *use_address);
|
||||||
void get_eth_mac_address_raw(uint8_t *mac);
|
void get_eth_mac_address_raw(uint8_t *mac);
|
||||||
std::string get_eth_mac_address_pretty();
|
std::string get_eth_mac_address_pretty();
|
||||||
eth_duplex_t get_duplex_mode();
|
eth_duplex_t get_duplex_mode();
|
||||||
@@ -114,7 +114,6 @@ class EthernetComponent : public Component {
|
|||||||
/// @brief Set arbitratry PHY registers from config.
|
/// @brief Set arbitratry PHY registers from config.
|
||||||
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
|
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
|
||||||
|
|
||||||
std::string use_address_;
|
|
||||||
#ifdef USE_ETHERNET_SPI
|
#ifdef USE_ETHERNET_SPI
|
||||||
uint8_t clk_pin_;
|
uint8_t clk_pin_;
|
||||||
uint8_t miso_pin_;
|
uint8_t miso_pin_;
|
||||||
@@ -158,6 +157,11 @@ class EthernetComponent : public Component {
|
|||||||
esp_eth_handle_t eth_handle_;
|
esp_eth_handle_t eth_handle_;
|
||||||
esp_eth_phy_t *phy_{nullptr};
|
esp_eth_phy_t *phy_{nullptr};
|
||||||
optional<std::array<uint8_t, 6>> fixed_mac_;
|
optional<std::array<uint8_t, 6>> fixed_mac_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Stores a pointer to a string literal (static storage duration).
|
||||||
|
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||||
|
const char *use_address_{""};
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(std::string, event_type)
|
TEMPLATABLE_VALUE(std::string, event_type)
|
||||||
|
|
||||||
void play(Ts... x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
void play(const Ts &...x) override { this->parent_->trigger(this->event_type_.value(x...)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class EventTrigger : public Trigger<std::string> {
|
class EventTrigger : public Trigger<std::string> {
|
||||||
|
|||||||
@@ -17,35 +17,35 @@ class LedTrigger : public Trigger<bool> {
|
|||||||
class CustomTrigger : public Trigger<std::string> {
|
class CustomTrigger : public Trigger<std::string> {
|
||||||
public:
|
public:
|
||||||
explicit CustomTrigger(EZOSensor *ezo) {
|
explicit CustomTrigger(EZOSensor *ezo) {
|
||||||
ezo->add_custom_callback([this](std::string value) { this->trigger(std::move(value)); });
|
ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class TTrigger : public Trigger<std::string> {
|
class TTrigger : public Trigger<std::string> {
|
||||||
public:
|
public:
|
||||||
explicit TTrigger(EZOSensor *ezo) {
|
explicit TTrigger(EZOSensor *ezo) {
|
||||||
ezo->add_t_callback([this](std::string value) { this->trigger(std::move(value)); });
|
ezo->add_t_callback([this](const std::string &value) { this->trigger(value); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CalibrationTrigger : public Trigger<std::string> {
|
class CalibrationTrigger : public Trigger<std::string> {
|
||||||
public:
|
public:
|
||||||
explicit CalibrationTrigger(EZOSensor *ezo) {
|
explicit CalibrationTrigger(EZOSensor *ezo) {
|
||||||
ezo->add_calibration_callback([this](std::string value) { this->trigger(std::move(value)); });
|
ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class SlopeTrigger : public Trigger<std::string> {
|
class SlopeTrigger : public Trigger<std::string> {
|
||||||
public:
|
public:
|
||||||
explicit SlopeTrigger(EZOSensor *ezo) {
|
explicit SlopeTrigger(EZOSensor *ezo) {
|
||||||
ezo->add_slope_callback([this](std::string value) { this->trigger(std::move(value)); });
|
ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeviceInformationTrigger : public Trigger<std::string> {
|
class DeviceInformationTrigger : public Trigger<std::string> {
|
||||||
public:
|
public:
|
||||||
explicit DeviceInformationTrigger(EZOSensor *ezo) {
|
explicit DeviceInformationTrigger(EZOSensor *ezo) {
|
||||||
ezo->add_device_infomation_callback([this](std::string value) { this->trigger(std::move(value)); });
|
ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ template<typename... Ts> class EzoPMPFindAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->find(); }
|
void play(const Ts &...x) override { this->ezopmp_->find(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -129,7 +129,7 @@ template<typename... Ts> class EzoPMPDoseContinuouslyAction : public Action<Ts..
|
|||||||
public:
|
public:
|
||||||
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->dose_continuously(); }
|
void play(const Ts &...x) override { this->ezopmp_->dose_continuously(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -139,7 +139,7 @@ template<typename... Ts> class EzoPMPDoseVolumeAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
|
void play(const Ts &...x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
|
||||||
TEMPLATABLE_VALUE(double, volume)
|
TEMPLATABLE_VALUE(double, volume)
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -150,7 +150,7 @@ template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction : public Action<Ts
|
|||||||
public:
|
public:
|
||||||
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
|
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
|
||||||
}
|
}
|
||||||
TEMPLATABLE_VALUE(double, volume)
|
TEMPLATABLE_VALUE(double, volume)
|
||||||
@@ -164,7 +164,7 @@ template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction : public Act
|
|||||||
public:
|
public:
|
||||||
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
|
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
|
||||||
}
|
}
|
||||||
TEMPLATABLE_VALUE(double, volume)
|
TEMPLATABLE_VALUE(double, volume)
|
||||||
@@ -178,7 +178,7 @@ template<typename... Ts> class EzoPMPSetCalibrationVolumeAction : public Action<
|
|||||||
public:
|
public:
|
||||||
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
|
void play(const Ts &...x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
|
||||||
TEMPLATABLE_VALUE(double, volume)
|
TEMPLATABLE_VALUE(double, volume)
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -189,7 +189,7 @@ template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction : public Ac
|
|||||||
public:
|
public:
|
||||||
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); }
|
void play(const Ts &...x) override { this->ezopmp_->clear_total_volume_dosed(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -199,7 +199,7 @@ template<typename... Ts> class EzoPMPClearCalibrationAction : public Action<Ts..
|
|||||||
public:
|
public:
|
||||||
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->clear_calibration(); }
|
void play(const Ts &...x) override { this->ezopmp_->clear_calibration(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -209,7 +209,7 @@ template<typename... Ts> class EzoPMPPauseDosingAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->pause_dosing(); }
|
void play(const Ts &...x) override { this->ezopmp_->pause_dosing(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -219,7 +219,7 @@ template<typename... Ts> class EzoPMPStopDosingAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->stop_dosing(); }
|
void play(const Ts &...x) override { this->ezopmp_->stop_dosing(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EzoPMP *ezopmp_;
|
EzoPMP *ezopmp_;
|
||||||
@@ -229,7 +229,7 @@ template<typename... Ts> class EzoPMPChangeI2CAddressAction : public Action<Ts..
|
|||||||
public:
|
public:
|
||||||
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
|
void play(const Ts &...x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
|
||||||
TEMPLATABLE_VALUE(int, address)
|
TEMPLATABLE_VALUE(int, address)
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -240,7 +240,7 @@ template<typename... Ts> class EzoPMPArbitraryCommandAction : public Action<Ts..
|
|||||||
public:
|
public:
|
||||||
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
|
void play(const Ts &...x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
|
||||||
TEMPLATABLE_VALUE(std::string, command)
|
TEMPLATABLE_VALUE(std::string, command)
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
|||||||
TEMPLATABLE_VALUE(int, speed)
|
TEMPLATABLE_VALUE(int, speed)
|
||||||
TEMPLATABLE_VALUE(FanDirection, direction)
|
TEMPLATABLE_VALUE(FanDirection, direction)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
auto call = this->state_->turn_on();
|
auto call = this->state_->turn_on();
|
||||||
if (this->oscillating_.has_value()) {
|
if (this->oscillating_.has_value()) {
|
||||||
call.set_oscillating(this->oscillating_.value(x...));
|
call.set_oscillating(this->oscillating_.value(x...));
|
||||||
@@ -36,7 +36,7 @@ template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit TurnOffAction(Fan *state) : state_(state) {}
|
explicit TurnOffAction(Fan *state) : state_(state) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->state_->turn_off().perform(); }
|
void play(const Ts &...x) override { this->state_->turn_off().perform(); }
|
||||||
|
|
||||||
Fan *state_;
|
Fan *state_;
|
||||||
};
|
};
|
||||||
@@ -45,7 +45,7 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
|||||||
public:
|
public:
|
||||||
explicit ToggleAction(Fan *state) : state_(state) {}
|
explicit ToggleAction(Fan *state) : state_(state) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->state_->toggle().perform(); }
|
void play(const Ts &...x) override { this->state_->toggle().perform(); }
|
||||||
|
|
||||||
Fan *state_;
|
Fan *state_;
|
||||||
};
|
};
|
||||||
@@ -56,7 +56,7 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
|
|||||||
|
|
||||||
TEMPLATABLE_VALUE(bool, no_off_cycle)
|
TEMPLATABLE_VALUE(bool, no_off_cycle)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(const Ts &...x) override {
|
||||||
// check to see if fan supports speeds and is on
|
// check to see if fan supports speeds and is on
|
||||||
if (this->state_->get_traits().supported_speed_count()) {
|
if (this->state_->get_traits().supported_speed_count()) {
|
||||||
if (this->state_->state) {
|
if (this->state_->state) {
|
||||||
@@ -97,7 +97,7 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
|
|||||||
template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> {
|
template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit FanIsOnCondition(Fan *state) : state_(state) {}
|
explicit FanIsOnCondition(Fan *state) : state_(state) {}
|
||||||
bool check(Ts... x) override { return this->state_->state; }
|
bool check(const Ts &...x) override { return this->state_->state; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Fan *state_;
|
Fan *state_;
|
||||||
@@ -105,7 +105,7 @@ template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> {
|
|||||||
template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
|
template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit FanIsOffCondition(Fan *state) : state_(state) {}
|
explicit FanIsOffCondition(Fan *state) : state_(state) {}
|
||||||
bool check(Ts... x) override { return !this->state_->state; }
|
bool check(const Ts &...x) override { return !this->state_->state; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Fan *state_;
|
Fan *state_;
|
||||||
@@ -212,18 +212,19 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
|||||||
public:
|
public:
|
||||||
FanPresetSetTrigger(Fan *state) {
|
FanPresetSetTrigger(Fan *state) {
|
||||||
state->add_on_state_callback([this, state]() {
|
state->add_on_state_callback([this, state]() {
|
||||||
auto preset_mode = state->preset_mode;
|
const auto *preset_mode = state->get_preset_mode();
|
||||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||||
this->last_preset_mode_ = preset_mode;
|
this->last_preset_mode_ = preset_mode;
|
||||||
if (should_trigger) {
|
if (should_trigger) {
|
||||||
this->trigger(preset_mode);
|
// Trigger with empty string when nullptr to maintain backward compatibility
|
||||||
|
this->trigger(preset_mode != nullptr ? preset_mode : "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this->last_preset_mode_ = state->preset_mode;
|
this->last_preset_mode_ = state->get_preset_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string last_preset_mode_;
|
const char *last_preset_mode_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fan
|
} // namespace fan
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user