mirror of
https://github.com/esphome/esphome.git
synced 2025-09-04 19:04:05 +00:00
Compare commits
1 Commits
integratio
...
optimize_e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fb1c2467b9 |
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
## 2. Core Technologies & Stack
|
## 2. Core Technologies & Stack
|
||||||
|
|
||||||
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
* **Languages:** Python (>=3.10), C++ (gnu++20)
|
||||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||||
* **Configuration:** YAML.
|
* **Configuration:** YAML.
|
||||||
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
||||||
|
|
||||||
* **Platform Support:**
|
* **Platform Support:**
|
||||||
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
|
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks.
|
||||||
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
|
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
|
||||||
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
|
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
|
||||||
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
||||||
@@ -60,7 +60,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
├── __init__.py # Component configuration schema and code generation
|
├── __init__.py # Component configuration schema and code generation
|
||||||
├── [component].h # C++ header file (if needed)
|
├── [component].h # C++ header file (if needed)
|
||||||
├── [component].cpp # C++ implementation (if needed)
|
├── [component].cpp # C++ implementation (if needed)
|
||||||
└── [platform]/ # Platform-specific implementations
|
└── [platform]/ # Platform-specific implementations
|
||||||
├── __init__.py # Platform-specific configuration
|
├── __init__.py # Platform-specific configuration
|
||||||
├── [platform].h # Platform C++ header
|
├── [platform].h # Platform C++ header
|
||||||
└── [platform].cpp # Platform C++ implementation
|
└── [platform].cpp # Platform C++ implementation
|
||||||
@@ -150,8 +150,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
* **Configuration Validation:**
|
* **Configuration Validation:**
|
||||||
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
||||||
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
||||||
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
|
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
|
||||||
* **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
|
|
||||||
* **Schema Extensions:**
|
* **Schema Extensions:**
|
||||||
```python
|
```python
|
||||||
CONFIG_SCHEMA = cv.Schema({ ... })
|
CONFIG_SCHEMA = cv.Schema({ ... })
|
||||||
|
@@ -1 +1 @@
|
|||||||
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
|
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
|
||||||
|
30
.github/workflows/auto-label-pr.yml
vendored
30
.github/workflows/auto-label-pr.yml
vendored
@@ -105,9 +105,7 @@ jobs:
|
|||||||
|
|
||||||
// Calculate data from PR files
|
// Calculate data from PR files
|
||||||
const changedFiles = prFiles.map(file => file.filename);
|
const changedFiles = prFiles.map(file => file.filename);
|
||||||
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
const totalChanges = totalAdditions + totalDeletions;
|
|
||||||
|
|
||||||
console.log('Current labels:', currentLabels.join(', '));
|
console.log('Current labels:', currentLabels.join(', '));
|
||||||
console.log('Changed files:', changedFiles.length);
|
console.log('Changed files:', changedFiles.length);
|
||||||
@@ -233,21 +231,16 @@ jobs:
|
|||||||
// Strategy: PR size detection
|
// Strategy: PR size detection
|
||||||
async function detectPRSize() {
|
async function detectPRSize() {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
const testChanges = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
|
|
||||||
|
const nonTestChanges = totalChanges - testChanges;
|
||||||
|
|
||||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||||
labels.add('small-pr');
|
labels.add('small-pr');
|
||||||
return labels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testAdditions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
|
||||||
const testDeletions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
|
|
||||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
|
||||||
|
|
||||||
// Don't add too-big if mega-pr label is already present
|
// Don't add too-big if mega-pr label is already present
|
||||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||||
labels.add('too-big');
|
labels.add('too-big');
|
||||||
@@ -382,7 +375,7 @@ jobs:
|
|||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
|
||||||
// Check for missing tests
|
// Check for missing tests
|
||||||
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
|
||||||
labels.add('needs-tests');
|
labels.add('needs-tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,13 +412,10 @@ jobs:
|
|||||||
|
|
||||||
// Too big message
|
// Too big message
|
||||||
if (finalLabels.includes('too-big')) {
|
if (finalLabels.includes('too-big')) {
|
||||||
const testAdditions = prFiles
|
const testChanges = prFiles
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
const testDeletions = prFiles
|
const nonTestChanges = totalChanges - testChanges;
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
|
||||||
|
|
||||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -156,7 +156,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5.5.0
|
uses: codecov/codecov-action@v5.4.3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
|
24
.github/workflows/needs-docs.yml
vendored
Normal file
24
.github/workflows/needs-docs.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Needs Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, unlabeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check for needs-docs label
|
||||||
|
uses: actions/github-script@v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number
|
||||||
|
});
|
||||||
|
const needsDocs = labels.find(label => label.name === 'needs-docs');
|
||||||
|
if (needsDocs) {
|
||||||
|
core.setFailed('Pull request needs docs');
|
||||||
|
}
|
30
.github/workflows/status-check-labels.yml
vendored
30
.github/workflows/status-check-labels.yml
vendored
@@ -1,30 +0,0 @@
|
|||||||
name: Status check labels
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [labeled, unlabeled]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
name: Check ${{ matrix.label }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
label:
|
|
||||||
- needs-docs
|
|
||||||
- merge-after-release
|
|
||||||
steps:
|
|
||||||
- name: Check for ${{ matrix.label }} label
|
|
||||||
uses: actions/github-script@v7.0.1
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number
|
|
||||||
});
|
|
||||||
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
|
|
||||||
if (hasLabel) {
|
|
||||||
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
|
|
||||||
}
|
|
@@ -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.12.11
|
rev: v0.12.9
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
@@ -89,7 +89,6 @@ esphome/components/bp5758d/* @Cossid
|
|||||||
esphome/components/button/* @esphome/core
|
esphome/components/button/* @esphome/core
|
||||||
esphome/components/bytebuffer/* @clydebarrow
|
esphome/components/bytebuffer/* @clydebarrow
|
||||||
esphome/components/camera/* @DT-art1 @bdraco
|
esphome/components/camera/* @DT-art1 @bdraco
|
||||||
esphome/components/camera_encoder/* @DT-art1
|
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/cap1188/* @mreditor97
|
esphome/components/cap1188/* @mreditor97
|
||||||
esphome/components/captive_portal/* @esphome/core
|
esphome/components/captive_portal/* @esphome/core
|
||||||
@@ -342,7 +341,7 @@ esphome/components/ota/* @esphome/core
|
|||||||
esphome/components/output/* @esphome/core
|
esphome/components/output/* @esphome/core
|
||||||
esphome/components/packet_transport/* @clydebarrow
|
esphome/components/packet_transport/* @clydebarrow
|
||||||
esphome/components/pca6416a/* @Mat931
|
esphome/components/pca6416a/* @Mat931
|
||||||
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
|
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||||
esphome/components/pcf85063/* @brogon
|
esphome/components/pcf85063/* @brogon
|
||||||
esphome/components/pcf8563/* @KoenBreeman
|
esphome/components/pcf8563/* @KoenBreeman
|
||||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||||
|
@@ -132,17 +132,14 @@ def choose_upload_log_host(
|
|||||||
]
|
]
|
||||||
resolved.append(choose_prompt(options, purpose=purpose))
|
resolved.append(choose_prompt(options, purpose=purpose))
|
||||||
elif device == "OTA":
|
elif device == "OTA":
|
||||||
if CORE.address and (
|
if (show_ota and "ota" in CORE.config) or (
|
||||||
(show_ota and "ota" in CORE.config)
|
show_api and "api" in CORE.config
|
||||||
or (show_api and "api" in CORE.config)
|
|
||||||
):
|
):
|
||||||
resolved.append(CORE.address)
|
resolved.append(CORE.address)
|
||||||
elif show_mqtt and has_mqtt_logging():
|
elif show_mqtt and has_mqtt_logging():
|
||||||
resolved.append("MQTT")
|
resolved.append("MQTT")
|
||||||
else:
|
else:
|
||||||
resolved.append(device)
|
resolved.append(device)
|
||||||
if not resolved:
|
|
||||||
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
|
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
# No devices specified, show interactive chooser
|
# No devices specified, show interactive chooser
|
||||||
@@ -479,7 +476,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
|||||||
from esphome.components.api.client import run_logs
|
from esphome.components.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, addresses_to_use)
|
return run_logs(config, addresses_to_use)
|
||||||
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
|
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.show_logs(
|
return mqtt.show_logs(
|
||||||
@@ -527,13 +524,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
|
|||||||
|
|
||||||
|
|
||||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
# Set memory analysis options in config
|
|
||||||
if args.analyze_memory:
|
|
||||||
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
|
|
||||||
|
|
||||||
if args.memory_report:
|
|
||||||
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
|
|
||||||
|
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -944,17 +934,6 @@ def parse_args(argv):
|
|||||||
help="Only generate source code, do not compile.",
|
help="Only generate source code, do not compile.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
parser_compile.add_argument(
|
|
||||||
"--analyze-memory",
|
|
||||||
help="Analyze and display memory usage by component after compilation.",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
parser_compile.add_argument(
|
|
||||||
"--memory-report",
|
|
||||||
help="Save memory analysis report to a file (supports .json or .txt).",
|
|
||||||
type=str,
|
|
||||||
metavar="FILE",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser(
|
parser_upload = subparsers.add_parser(
|
||||||
"upload",
|
"upload",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -61,10 +61,11 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
}
|
}
|
||||||
if (no_humidity) {
|
if (no_humidity) {
|
||||||
ESP_LOGW(TAG, "No valid state from humidity sensor!");
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
}
|
}
|
||||||
|
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_warning("Unable to calculate absolute humidity.");
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +87,9 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
es = es_wobus(temperature_c);
|
es = es_wobus(temperature_c);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_error("Invalid saturation vapor pressure equation selection!");
|
this->status_set_error();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||||
|
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
|
|||||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||||
uint8_t rev_newaddress = ~newaddress;
|
uint8_t rev_newaddress = ~newaddress;
|
||||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
||||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
data[4] = calc_crc8_(data, 4);
|
||||||
if (!this->write_bytes(REG_ADDRESS, data)) {
|
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
|
|||||||
|
|
||||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
data[4] = calc_crc8_(data, 4);
|
||||||
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
|||||||
auto res = *data;
|
auto res = *data;
|
||||||
auto crc_byte = res[len];
|
auto crc_byte = res[len];
|
||||||
|
|
||||||
if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
|
if (crc_byte != calc_crc8_(res, len)) {
|
||||||
this->error_code_ = CRC_CHECK_FAILED;
|
this->error_code_ = CRC_CHECK_FAILED;
|
||||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||||
return optional<std::array<uint8_t, N>>();
|
return optional<std::array<uint8_t, N>>();
|
||||||
@@ -192,5 +192,20 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
|
||||||
|
uint8_t i, byte1, crc = 0xFF;
|
||||||
|
for (byte1 = 0; byte1 < num; byte1++) {
|
||||||
|
crc ^= (dat[byte1]);
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if (crc & 0x80) {
|
||||||
|
crc = (crc << 1) ^ 0x31;
|
||||||
|
} else {
|
||||||
|
crc = (crc << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
} // namespace ags10
|
} // namespace ags10
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ags10 {
|
namespace ags10 {
|
||||||
@@ -99,6 +99,16 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
|||||||
* Read, checks and returns data from the sensor.
|
* Read, checks and returns data from the sensor.
|
||||||
*/
|
*/
|
||||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates CRC8 value.
|
||||||
|
*
|
||||||
|
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
|
||||||
|
*
|
||||||
|
* @param[in] dat the data buffer
|
||||||
|
* @param num number of bytes in the buffer
|
||||||
|
*/
|
||||||
|
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||||
|
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await esp32_ble_tracker.register_ble_device(var, config)
|
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
@@ -13,7 +13,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(alarm_control_panel_ns.using)
|
cg.add_global(alarm_control_panel_ns.using)
|
||||||
|
@@ -29,6 +29,22 @@ namespace am2315c {
|
|||||||
|
|
||||||
static const char *const TAG = "am2315c";
|
static const char *const TAG = "am2315c";
|
||||||
|
|
||||||
|
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
|
||||||
|
uint8_t crc = 0xFF;
|
||||||
|
while (len--) {
|
||||||
|
crc ^= *data++;
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
if (crc & 0x80) {
|
||||||
|
crc <<= 1;
|
||||||
|
crc ^= 0x31;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
bool AM2315C::reset_register_(uint8_t reg) {
|
bool AM2315C::reset_register_(uint8_t reg) {
|
||||||
// code based on demo code sent by www.aosong.com
|
// code based on demo code sent by www.aosong.com
|
||||||
// no further documentation.
|
// no further documentation.
|
||||||
@@ -70,7 +86,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
|||||||
humidity = raw * 9.5367431640625e-5;
|
humidity = raw * 9.5367431640625e-5;
|
||||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||||
temperature = raw * 1.9073486328125e-4 - 50;
|
temperature = raw * 1.9073486328125e-4 - 50;
|
||||||
return crc8(data, 6, 0xFF, 0x31, true) == data[6];
|
return this->crc8_(data, 6) == data[6];
|
||||||
}
|
}
|
||||||
|
|
||||||
void AM2315C::setup() {
|
void AM2315C::setup() {
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace am2315c {
|
namespace am2315c {
|
||||||
@@ -39,6 +39,7 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
|||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
uint8_t crc8_(uint8_t *data, uint8_t len);
|
||||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||||
bool reset_register_(uint8_t reg);
|
bool reset_register_(uint8_t reg);
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
@@ -122,8 +122,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
|
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
|
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
@@ -136,7 +134,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.WEB)
|
@coroutine_with_priority(40.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
@@ -323,7 +321,6 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
|||||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
cg.add(var.set_service("esphome.tag_scanned"))
|
cg.add(var.set_service("esphome.tag_scanned"))
|
||||||
|
@@ -1712,7 +1712,6 @@ message BluetoothScannerStateResponse {
|
|||||||
|
|
||||||
BluetoothScannerState state = 1;
|
BluetoothScannerState state = 1;
|
||||||
BluetoothScannerMode mode = 2;
|
BluetoothScannerMode mode = 2;
|
||||||
BluetoothScannerMode configured_mode = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothScannerSetModeRequest {
|
message BluetoothScannerSetModeRequest {
|
||||||
|
@@ -112,7 +112,7 @@ void APIConnection::start() {
|
|||||||
APIError err = this->helper_->init();
|
APIError err = this->helper_->init();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_(LOG_STR("Helper init failed"), err);
|
this->log_warning_("Helper init failed", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->client_info_.peername = helper_->getpeername();
|
this->client_info_.peername = helper_->getpeername();
|
||||||
@@ -159,7 +159,7 @@ void APIConnection::loop() {
|
|||||||
break;
|
break;
|
||||||
} else if (err != APIError::OK) {
|
} else if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_(LOG_STR("Reading failed"), err);
|
this->log_warning_("Reading failed", err);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this->last_traffic_ = now;
|
this->last_traffic_ = now;
|
||||||
@@ -465,7 +465,9 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
|||||||
resp.cold_white = values.get_cold_white();
|
resp.cold_white = values.get_cold_white();
|
||||||
resp.warm_white = values.get_warm_white();
|
resp.warm_white = values.get_warm_white();
|
||||||
if (light->supports_effects()) {
|
if (light->supports_effects()) {
|
||||||
resp.set_effect(light->get_effect_name_ref());
|
// get_effect_name() returns temporary std::string - must store it
|
||||||
|
std::string effect_name = light->get_effect_name();
|
||||||
|
resp.set_effect(StringRef(effect_name));
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@@ -1423,7 +1425,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||||
|
|
||||||
resp.set_compilation_time(App.get_compilation_time_ref());
|
// get_compilation_time() returns temporary std::string - must store it
|
||||||
|
std::string compilation_time = App.get_compilation_time();
|
||||||
|
resp.set_compilation_time(StringRef(compilation_time));
|
||||||
|
|
||||||
// Compile-time StringRef constants for manufacturers
|
// Compile-time StringRef constants for manufacturers
|
||||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||||
@@ -1565,7 +1569,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
return false;
|
return false;
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_(LOG_STR("Packet write failed"), err);
|
this->log_warning_("Packet write failed", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Do not set last_traffic_ on send
|
// Do not set last_traffic_ on send
|
||||||
@@ -1752,7 +1756,7 @@ void APIConnection::process_batch_() {
|
|||||||
std::span<const PacketInfo>(packet_info, packet_count));
|
std::span<const PacketInfo>(packet_info, packet_count));
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_(LOG_STR("Batch write failed"), err);
|
this->log_warning_("Batch write failed", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -1830,14 +1834,11 @@ void APIConnection::process_state_subscriptions_() {
|
|||||||
}
|
}
|
||||||
#endif // USE_API_HOMEASSISTANT_STATES
|
#endif // USE_API_HOMEASSISTANT_STATES
|
||||||
|
|
||||||
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
void APIConnection::log_warning_(const char *message, APIError err) {
|
||||||
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message),
|
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
|
||||||
LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::log_socket_operation_failed_(APIError err) {
|
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
|
||||||
this->log_warning_(LOG_STR("Socket operation failed"), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
#endif
|
#endif
|
||||||
|
@@ -44,7 +44,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HO
|
|||||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class APIConnection final : public APIServerConnection {
|
class APIConnection : public APIServerConnection {
|
||||||
public:
|
public:
|
||||||
friend class APIServer;
|
friend class APIServer;
|
||||||
friend class ListEntitiesIterator;
|
friend class ListEntitiesIterator;
|
||||||
@@ -301,17 +301,9 @@ class APIConnection final : public APIServerConnection {
|
|||||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||||
// Set common fields that are shared by all entity types
|
// Set common fields that are shared by all entity types
|
||||||
msg.key = entity->get_object_id_hash();
|
msg.key = entity->get_object_id_hash();
|
||||||
// Try to use static reference first to avoid allocation
|
// IMPORTANT: get_object_id() may return a temporary std::string
|
||||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
std::string object_id = entity->get_object_id();
|
||||||
// Store dynamic string outside the if-else to maintain lifetime
|
msg.set_object_id(StringRef(object_id));
|
||||||
std::string object_id;
|
|
||||||
if (!static_ref.empty()) {
|
|
||||||
msg.set_object_id(static_ref);
|
|
||||||
} else {
|
|
||||||
// Dynamic case - need to allocate
|
|
||||||
object_id = entity->get_object_id();
|
|
||||||
msg.set_object_id(StringRef(object_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity->has_own_name()) {
|
if (entity->has_own_name()) {
|
||||||
msg.set_name(entity->get_name());
|
msg.set_name(entity->get_name());
|
||||||
@@ -732,7 +724,7 @@ class APIConnection final : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to log API errors with errno
|
// Helper function to log API errors with errno
|
||||||
void log_warning_(const LogString *message, APIError err);
|
void log_warning_(const char *message, APIError err);
|
||||||
// Specific helper for duplicated error message
|
// Specific helper for duplicated error message
|
||||||
void log_socket_operation_failed_(APIError err);
|
void log_socket_operation_failed_(APIError err);
|
||||||
};
|
};
|
||||||
|
@@ -23,59 +23,59 @@ static const char *const TAG = "api.frame_helper";
|
|||||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const LogString *api_error_to_logstr(APIError err) {
|
const char *api_error_to_str(APIError err) {
|
||||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||||
if (err == APIError::OK) {
|
if (err == APIError::OK) {
|
||||||
return LOG_STR("OK");
|
return "OK";
|
||||||
} else if (err == APIError::WOULD_BLOCK) {
|
} else if (err == APIError::WOULD_BLOCK) {
|
||||||
return LOG_STR("WOULD_BLOCK");
|
return "WOULD_BLOCK";
|
||||||
} else if (err == APIError::BAD_INDICATOR) {
|
} else if (err == APIError::BAD_INDICATOR) {
|
||||||
return LOG_STR("BAD_INDICATOR");
|
return "BAD_INDICATOR";
|
||||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||||
return LOG_STR("BAD_DATA_PACKET");
|
return "BAD_DATA_PACKET";
|
||||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||||
return LOG_STR("TCP_NODELAY_FAILED");
|
return "TCP_NODELAY_FAILED";
|
||||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||||
return LOG_STR("TCP_NONBLOCKING_FAILED");
|
return "TCP_NONBLOCKING_FAILED";
|
||||||
} else if (err == APIError::CLOSE_FAILED) {
|
} else if (err == APIError::CLOSE_FAILED) {
|
||||||
return LOG_STR("CLOSE_FAILED");
|
return "CLOSE_FAILED";
|
||||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||||
return LOG_STR("SHUTDOWN_FAILED");
|
return "SHUTDOWN_FAILED";
|
||||||
} else if (err == APIError::BAD_STATE) {
|
} else if (err == APIError::BAD_STATE) {
|
||||||
return LOG_STR("BAD_STATE");
|
return "BAD_STATE";
|
||||||
} else if (err == APIError::BAD_ARG) {
|
} else if (err == APIError::BAD_ARG) {
|
||||||
return LOG_STR("BAD_ARG");
|
return "BAD_ARG";
|
||||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||||
return LOG_STR("SOCKET_READ_FAILED");
|
return "SOCKET_READ_FAILED";
|
||||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||||
return LOG_STR("SOCKET_WRITE_FAILED");
|
return "SOCKET_WRITE_FAILED";
|
||||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||||
return LOG_STR("OUT_OF_MEMORY");
|
return "OUT_OF_MEMORY";
|
||||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||||
return LOG_STR("CONNECTION_CLOSED");
|
return "CONNECTION_CLOSED";
|
||||||
}
|
}
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
|
return "BAD_HANDSHAKE_PACKET_LEN";
|
||||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||||
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
|
return "HANDSHAKESTATE_READ_FAILED";
|
||||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||||
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
|
return "HANDSHAKESTATE_WRITE_FAILED";
|
||||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||||
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
|
return "HANDSHAKESTATE_BAD_STATE";
|
||||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||||
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
|
return "CIPHERSTATE_DECRYPT_FAILED";
|
||||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||||
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
|
return "CIPHERSTATE_ENCRYPT_FAILED";
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||||
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
|
return "HANDSHAKESTATE_SETUP_FAILED";
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||||
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
|
return "HANDSHAKESTATE_SPLIT_FAILED";
|
||||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||||
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
|
return "BAD_HANDSHAKE_ERROR_BYTE";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return LOG_STR("UNKNOWN");
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation for loop - handles sending buffered data
|
// Default implementation for loop - handles sending buffered data
|
||||||
|
@@ -66,7 +66,7 @@ enum class APIError : uint16_t {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogString *api_error_to_logstr(APIError err);
|
const char *api_error_to_str(APIError err);
|
||||||
|
|
||||||
class APIFrameHelper {
|
class APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
@@ -104,9 +104,9 @@ class APIFrameHelper {
|
|||||||
// The buffer contains all messages with appropriate padding before each
|
// The buffer contains all messages with appropriate padding before each
|
||||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||||
// Get the frame header padding required by this protocol
|
// Get the frame header padding required by this protocol
|
||||||
uint8_t frame_header_padding() const { return frame_header_padding_; }
|
virtual uint8_t frame_header_padding() = 0;
|
||||||
// Get the frame footer size required by this protocol
|
// Get the frame footer size required by this protocol
|
||||||
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
virtual uint8_t frame_footer_size() = 0;
|
||||||
// Check if socket has data ready to read
|
// Check if socket has data ready to read
|
||||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||||
|
|
||||||
|
@@ -27,42 +27,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Convert a noise error code to a readable error
|
/// Convert a noise error code to a readable error
|
||||||
const LogString *noise_err_to_logstr(int err) {
|
std::string noise_err_to_str(int err) {
|
||||||
if (err == NOISE_ERROR_NO_MEMORY)
|
if (err == NOISE_ERROR_NO_MEMORY)
|
||||||
return LOG_STR("NO_MEMORY");
|
return "NO_MEMORY";
|
||||||
if (err == NOISE_ERROR_UNKNOWN_ID)
|
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||||
return LOG_STR("UNKNOWN_ID");
|
return "UNKNOWN_ID";
|
||||||
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||||
return LOG_STR("UNKNOWN_NAME");
|
return "UNKNOWN_NAME";
|
||||||
if (err == NOISE_ERROR_MAC_FAILURE)
|
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||||
return LOG_STR("MAC_FAILURE");
|
return "MAC_FAILURE";
|
||||||
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||||
return LOG_STR("NOT_APPLICABLE");
|
return "NOT_APPLICABLE";
|
||||||
if (err == NOISE_ERROR_SYSTEM)
|
if (err == NOISE_ERROR_SYSTEM)
|
||||||
return LOG_STR("SYSTEM");
|
return "SYSTEM";
|
||||||
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||||
return LOG_STR("REMOTE_KEY_REQUIRED");
|
return "REMOTE_KEY_REQUIRED";
|
||||||
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||||
return LOG_STR("LOCAL_KEY_REQUIRED");
|
return "LOCAL_KEY_REQUIRED";
|
||||||
if (err == NOISE_ERROR_PSK_REQUIRED)
|
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||||
return LOG_STR("PSK_REQUIRED");
|
return "PSK_REQUIRED";
|
||||||
if (err == NOISE_ERROR_INVALID_LENGTH)
|
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||||
return LOG_STR("INVALID_LENGTH");
|
return "INVALID_LENGTH";
|
||||||
if (err == NOISE_ERROR_INVALID_PARAM)
|
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||||
return LOG_STR("INVALID_PARAM");
|
return "INVALID_PARAM";
|
||||||
if (err == NOISE_ERROR_INVALID_STATE)
|
if (err == NOISE_ERROR_INVALID_STATE)
|
||||||
return LOG_STR("INVALID_STATE");
|
return "INVALID_STATE";
|
||||||
if (err == NOISE_ERROR_INVALID_NONCE)
|
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||||
return LOG_STR("INVALID_NONCE");
|
return "INVALID_NONCE";
|
||||||
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||||
return LOG_STR("INVALID_PRIVATE_KEY");
|
return "INVALID_PRIVATE_KEY";
|
||||||
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||||
return LOG_STR("INVALID_PUBLIC_KEY");
|
return "INVALID_PUBLIC_KEY";
|
||||||
if (err == NOISE_ERROR_INVALID_FORMAT)
|
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||||
return LOG_STR("INVALID_FORMAT");
|
return "INVALID_FORMAT";
|
||||||
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||||
return LOG_STR("INVALID_SIGNATURE");
|
return "INVALID_SIGNATURE";
|
||||||
return LOG_STR("UNKNOWN");
|
return to_string(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the frame helper, returns OK if successful.
|
/// Initialize the frame helper, returns OK if successful.
|
||||||
@@ -83,18 +83,18 @@ APIError APINoiseFrameHelper::init() {
|
|||||||
// Helper for handling handshake frame errors
|
// Helper for handling handshake frame errors
|
||||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
|
send_explicit_handshake_reject_("Bad indicator byte");
|
||||||
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
|
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||||
}
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for handling noise library errors
|
// Helper for handling noise library errors
|
||||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
|
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
|
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
||||||
return api_err;
|
return api_err;
|
||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -279,11 +279,11 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frame.empty()) {
|
if (frame.empty()) {
|
||||||
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
send_explicit_handshake_reject_("Empty handshake message");
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
} else if (frame[0] != 0x00) {
|
} else if (frame[0] != 0x00) {
|
||||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||||
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,10 +293,8 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Special handling for MAC failure
|
// Special handling for MAC failure
|
||||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
|
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
||||||
: LOG_STR("Handshake error"));
|
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
||||||
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
|
|
||||||
APIError::HANDSHAKESTATE_READ_FAILED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aerr = check_handshake_finished_();
|
aerr = check_handshake_finished_();
|
||||||
@@ -309,8 +307,8 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||||
|
|
||||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||||
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
|
APIError aerr_write =
|
||||||
APIError::HANDSHAKESTATE_WRITE_FAILED);
|
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||||
if (aerr_write != APIError::OK)
|
if (aerr_write != APIError::OK)
|
||||||
return aerr_write;
|
return aerr_write;
|
||||||
buffer[0] = 0x00; // success
|
buffer[0] = 0x00; // success
|
||||||
@@ -333,31 +331,15 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
||||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
|
||||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
|
||||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
data.resize(reason_len + 1);
|
data.resize(reason.length() + 1);
|
||||||
data[0] = 0x01; // failure
|
|
||||||
|
|
||||||
// Copy error message from PROGMEM
|
|
||||||
if (reason_len > 0) {
|
|
||||||
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Normal memory access
|
|
||||||
const char *reason_str = LOG_STR_ARG(reason);
|
|
||||||
size_t reason_len = strlen(reason_str);
|
|
||||||
std::vector<uint8_t> data;
|
|
||||||
data.resize(reason_len + 1);
|
|
||||||
data[0] = 0x01; // failure
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
// Copy error message in bulk
|
// Copy error message in bulk
|
||||||
if (reason_len > 0) {
|
if (!reason.empty()) {
|
||||||
std::memcpy(data.data() + 1, reason_str, reason_len);
|
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// temporarily remove failed state
|
// temporarily remove failed state
|
||||||
auto orig_state = state_;
|
auto orig_state = state_;
|
||||||
@@ -386,8 +368,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||||
APIError decrypt_err =
|
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
|
||||||
if (decrypt_err != APIError::OK)
|
if (decrypt_err != APIError::OK)
|
||||||
return decrypt_err;
|
return decrypt_err;
|
||||||
|
|
||||||
@@ -469,8 +450,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
|||||||
4 + packet.payload_size + frame_footer_size_);
|
4 + packet.payload_size + frame_footer_size_);
|
||||||
|
|
||||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||||
APIError aerr =
|
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||||
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
@@ -524,27 +504,25 @@ APIError APINoiseFrameHelper::init_handshake_() {
|
|||||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||||
|
|
||||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||||
APIError aerr =
|
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
const auto &psk = ctx_->get_psk();
|
const auto &psk = ctx_->get_psk();
|
||||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
|
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
APIError::HANDSHAKESTATE_SETUP_FAILED);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||||
prologue_ = {};
|
prologue_ = {};
|
||||||
|
|
||||||
err = noise_handshakestate_start(handshake_);
|
err = noise_handshakestate_start(handshake_);
|
||||||
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -562,8 +540,7 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
|||||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||||
}
|
}
|
||||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||||
APIError aerr =
|
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||||
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APINoiseFrameHelper final : public APIFrameHelper {
|
class APINoiseFrameHelper : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
||||||
const ClientInfo *client_info)
|
const ClientInfo *client_info)
|
||||||
@@ -25,6 +25,10 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
// Get the frame header padding required by this protocol
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError state_action_();
|
APIError state_action_();
|
||||||
@@ -32,9 +36,9 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
|||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
APIError init_handshake_();
|
APIError init_handshake_();
|
||||||
APIError check_handshake_finished_();
|
APIError check_handshake_finished_();
|
||||||
void send_explicit_handshake_reject_(const LogString *reason);
|
void send_explicit_handshake_reject_(const std::string &reason);
|
||||||
APIError handle_handshake_frame_error_(APIError aerr);
|
APIError handle_handshake_frame_error_(APIError aerr);
|
||||||
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
|
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
||||||
|
|
||||||
// Pointers first (4 bytes each)
|
// Pointers first (4 bytes each)
|
||||||
NoiseHandshakeState *handshake_{nullptr};
|
NoiseHandshakeState *handshake_{nullptr};
|
||||||
|
@@ -235,8 +235,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
|
|
||||||
for (const auto &packet : packets) {
|
for (const auto &packet : packets) {
|
||||||
// Calculate varint sizes for header layout
|
// Calculate varint sizes for header layout
|
||||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
uint8_t size_varint_len = api::ProtoSize::varint(packet.payload_size);
|
||||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
uint8_t type_varint_len = api::ProtoSize::varint(packet.message_type);
|
||||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||||
|
|
||||||
// Calculate where to start writing the header
|
// Calculate where to start writing the header
|
||||||
@@ -271,9 +271,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
buf_start[header_offset] = 0x00; // indicator
|
buf_start[header_offset] = 0x00; // indicator
|
||||||
|
|
||||||
// Encode varints directly into buffer
|
// Encode varints directly into buffer
|
||||||
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
encode_varint_unchecked(buf_start + header_offset + 1, packet.payload_size);
|
||||||
ProtoVarInt(packet.message_type)
|
encode_varint_unchecked(buf_start + header_offset + 1 + size_varint_len, packet.message_type);
|
||||||
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
|
||||||
|
|
||||||
// Add iovec for this packet (header + payload)
|
// Add iovec for this packet (header + payload)
|
||||||
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||||
: APIFrameHelper(std::move(socket), client_info) {
|
: APIFrameHelper(std::move(socket), client_info) {
|
||||||
@@ -22,6 +22,9 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
|
@@ -2153,12 +2153,10 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
|
|||||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||||
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
|
|
||||||
}
|
}
|
||||||
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
|
|
||||||
}
|
}
|
||||||
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1135,7 +1135,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "string_", this->string_);
|
dump_field(out, "string_", this->string_);
|
||||||
dump_field(out, "int_", this->int_);
|
dump_field(out, "int_", this->int_);
|
||||||
for (const auto it : this->bool_array) {
|
for (const auto it : this->bool_array) {
|
||||||
dump_field(out, "bool_array", static_cast<bool>(it), 4);
|
dump_field(out, "bool_array", it, 4);
|
||||||
}
|
}
|
||||||
for (const auto &it : this->int_array) {
|
for (const auto &it : this->int_array) {
|
||||||
dump_field(out, "int_array", it, 4);
|
dump_field(out, "int_array", it, 4);
|
||||||
@@ -1704,7 +1704,6 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
|
|||||||
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
||||||
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
||||||
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
||||||
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
|
|
||||||
}
|
}
|
||||||
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
||||||
|
@@ -8,70 +8,74 @@ namespace esphome::api {
|
|||||||
static const char *const TAG = "api.proto";
|
static const char *const TAG = "api.proto";
|
||||||
|
|
||||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
const uint8_t *ptr = buffer;
|
uint32_t i = 0;
|
||||||
const uint8_t *end = buffer + length;
|
bool error = false;
|
||||||
|
while (i < length) {
|
||||||
while (ptr < end) {
|
|
||||||
uint32_t consumed;
|
uint32_t consumed;
|
||||||
|
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
// Parse field header
|
|
||||||
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t tag = res->as_uint32();
|
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||||
uint32_t field_type = tag & 0b111;
|
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||||
uint32_t field_id = tag >> 3;
|
i += consumed;
|
||||||
ptr += consumed;
|
|
||||||
|
|
||||||
switch (field_type) {
|
switch (field_type) {
|
||||||
case 0: { // VarInt
|
case 0: { // VarInt
|
||||||
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
|
||||||
return;
|
error = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!this->decode_varint(field_id, *res)) {
|
if (!this->decode_varint(field_id, *res)) {
|
||||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
||||||
}
|
}
|
||||||
ptr += consumed;
|
i += consumed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: { // Length-delimited
|
case 2: { // Length-delimited
|
||||||
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
|
||||||
return;
|
error = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
uint32_t field_length = res->as_uint32();
|
uint32_t field_length = res->as_uint32();
|
||||||
ptr += consumed;
|
i += consumed;
|
||||||
if (ptr + field_length > end) {
|
if (field_length > length - i) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
|
||||||
return;
|
error = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
|
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||||
}
|
}
|
||||||
ptr += field_length;
|
i += field_length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: { // 32-bit
|
case 5: { // 32-bit
|
||||||
if (ptr + 4 > end) {
|
if (length - i < 4) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
|
||||||
return;
|
error = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||||
}
|
}
|
||||||
ptr += 4;
|
i += 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
|
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
|
||||||
return;
|
error = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -124,34 +124,6 @@ class ProtoVarInt {
|
|||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
return decode_zigzag64(this->value_);
|
return decode_zigzag64(this->value_);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
|
||||||
*
|
|
||||||
* @param buffer The pre-allocated buffer to write the encoded varint to
|
|
||||||
* @param len The size of the buffer in bytes
|
|
||||||
*
|
|
||||||
* @note The caller is responsible for ensuring the buffer is large enough
|
|
||||||
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
|
||||||
* the exact size needed before calling this method.
|
|
||||||
* @note No bounds checking is performed for performance reasons.
|
|
||||||
*/
|
|
||||||
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
|
||||||
uint64_t val = this->value_;
|
|
||||||
if (val <= 0x7F) {
|
|
||||||
buffer[0] = val;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
size_t i = 0;
|
|
||||||
while (val && i < len) {
|
|
||||||
uint8_t temp = val & 0x7F;
|
|
||||||
val >>= 7;
|
|
||||||
if (val) {
|
|
||||||
buffer[i++] = temp | 0x80;
|
|
||||||
} else {
|
|
||||||
buffer[i++] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void encode(std::vector<uint8_t> &out) {
|
void encode(std::vector<uint8_t> &out) {
|
||||||
uint64_t val = this->value_;
|
uint64_t val = this->value_;
|
||||||
if (val <= 0x7F) {
|
if (val <= 0x7F) {
|
||||||
@@ -330,6 +302,28 @@ class ProtoWriteBuffer {
|
|||||||
std::vector<uint8_t> *buffer_;
|
std::vector<uint8_t> *buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encode a uint16_t value as a varint directly to a buffer without bounds checking
|
||||||
|
*
|
||||||
|
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||||
|
* @param value The uint16_t value to encode (0-65535)
|
||||||
|
*
|
||||||
|
* @note The caller is responsible for ensuring the buffer is large enough (max 3 bytes for uint16_t)
|
||||||
|
* @note No bounds checking is performed for performance reasons
|
||||||
|
*/
|
||||||
|
inline void encode_varint_unchecked(uint8_t *buffer, uint16_t value) {
|
||||||
|
if (value < 128) {
|
||||||
|
buffer[0] = value;
|
||||||
|
} else if (value < 16384) {
|
||||||
|
buffer[0] = (value & 0x7F) | 0x80;
|
||||||
|
buffer[1] = value >> 7;
|
||||||
|
} else {
|
||||||
|
buffer[0] = (value & 0x7F) | 0x80;
|
||||||
|
buffer[1] = ((value >> 7) & 0x7F) | 0x80;
|
||||||
|
buffer[2] = value >> 14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ProtoSize;
|
class ProtoSize;
|
||||||
|
|
||||||
@@ -386,6 +380,33 @@ class ProtoSize {
|
|||||||
|
|
||||||
uint32_t get_size() const { return total_size_; }
|
uint32_t get_size() const { return total_size_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint8_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint8_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value (1 or 2)
|
||||||
|
*/
|
||||||
|
static constexpr uint8_t varint(uint8_t value) {
|
||||||
|
// For uint8_t (0-255), we need at most 2 bytes
|
||||||
|
return (value < 128) ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint16_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint16_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value (1-3)
|
||||||
|
*/
|
||||||
|
static constexpr uint8_t varint(uint16_t value) {
|
||||||
|
// For uint16_t (0-65535), we need at most 3 bytes
|
||||||
|
if (value < 128)
|
||||||
|
return 1; // 7 bits
|
||||||
|
else if (value < 16384)
|
||||||
|
return 2; // 14 bits
|
||||||
|
else
|
||||||
|
return 3; // 15-16 bits
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||||
*
|
*
|
||||||
@@ -395,11 +416,9 @@ class ProtoSize {
|
|||||||
static constexpr uint32_t varint(uint32_t value) {
|
static constexpr uint32_t varint(uint32_t value) {
|
||||||
// Optimized varint size calculation using leading zeros
|
// Optimized varint size calculation using leading zeros
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
if (value < 128)
|
if (value < 128) {
|
||||||
return 1; // 7 bits, common case for small values
|
return 1; // 7 bits, common case for small values
|
||||||
|
} else if (value < 16384) {
|
||||||
// For larger values, count bytes needed based on the position of the highest bit set
|
|
||||||
if (value < 16384) {
|
|
||||||
return 2; // 14 bits
|
return 2; // 14 bits
|
||||||
} else if (value < 2097152) {
|
} else if (value < 2097152) {
|
||||||
return 3; // 21 bits
|
return 3; // 21 bits
|
||||||
@@ -773,7 +792,7 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
|
|||||||
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
||||||
|
|
||||||
// Write the length varint directly
|
// Write the length varint directly
|
||||||
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
|
encode_varint_unchecked(this->buffer_->data() + begin, static_cast<uint16_t>(msg_length_bytes));
|
||||||
|
|
||||||
// Now encode the message content - it will append to the buffer
|
// Now encode the message content - it will append to the buffer
|
||||||
value.encode(*this);
|
value.encode(*this);
|
||||||
|
@@ -8,7 +8,7 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
|
@coroutine_with_priority(200.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
if CORE.is_esp32 or CORE.is_libretiny:
|
if CORE.is_esp32 or CORE.is_libretiny:
|
||||||
# https://github.com/ESP32Async/AsyncTCP
|
# https://github.com/ESP32Async/AsyncTCP
|
||||||
|
@@ -382,15 +382,20 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
|||||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||||
// Default is 143FH (20ms, 63ms)
|
// Default is 143FH (20ms, 63ms)
|
||||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
|
||||||
this->enable();
|
|
||||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
|
||||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||||
uint8_t addrl = (a_register & 0xFF);
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
||||||
this->transfer_array(data, 4);
|
this->transfer_array(data, 4);
|
||||||
uint16_t output = encode_uint16(data[2], data[3]);
|
uint16_t output = encode_uint16(data[2], data[3]);
|
||||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||||
|
this->enable();
|
||||||
|
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
||||||
|
uint16_t output = this->read16_transaction_(a_register);
|
||||||
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
||||||
this->disable();
|
this->disable();
|
||||||
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
||||||
@@ -398,8 +403,14 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||||
const uint16_t val_h = this->read16_(addr_h);
|
this->enable();
|
||||||
const uint16_t val_l = this->read16_(addr_l);
|
delay_microseconds_safe(1);
|
||||||
|
const uint16_t val_h = this->read16_transaction_(addr_h);
|
||||||
|
delay_microseconds_safe(1);
|
||||||
|
const uint16_t val_l = this->read16_transaction_(addr_l);
|
||||||
|
delay_microseconds_safe(1);
|
||||||
|
this->disable();
|
||||||
|
delay_microseconds_safe(1);
|
||||||
const int32_t val = (val_h << 16) | val_l;
|
const int32_t val = (val_h << 16) | val_l;
|
||||||
|
|
||||||
ESP_LOGVV(TAG,
|
ESP_LOGVV(TAG,
|
||||||
|
@@ -140,6 +140,7 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
||||||
#endif
|
#endif
|
||||||
uint16_t read16_(uint16_t a_register);
|
uint16_t read16_(uint16_t a_register);
|
||||||
|
uint16_t read16_transaction_(uint16_t a_register);
|
||||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||||
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
||||||
float get_local_phase_voltage_(uint8_t phase);
|
float get_local_phase_voltage_(uint8_t phase);
|
||||||
|
@@ -2,7 +2,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_ADC")
|
cg.add_define("USE_AUDIO_ADC")
|
||||||
cg.add_global(audio_adc_ns.using)
|
cg.add_global(audio_adc_ns.using)
|
||||||
|
@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_VOLUME
|
from esphome.const import CONF_ID, CONF_VOLUME
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_DAC")
|
cg.add_define("USE_AUDIO_DAC")
|
||||||
cg.add_global(audio_dac_ns.using)
|
cg.add_global(audio_dac_ns.using)
|
||||||
|
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
|
|||||||
i2c::ErrorCode err;
|
i2c::ErrorCode err;
|
||||||
uint8_t data[8]{};
|
uint8_t data[8]{};
|
||||||
|
|
||||||
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
|
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
err = this->read(data, sizeof(data));
|
err = this->read(data, sizeof(data));
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
|
@@ -59,7 +59,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_VIBRATION,
|
DEVICE_CLASS_VIBRATION,
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(binary_sensor_ns.using)
|
cg.add_global(binary_sensor_ns.using)
|
||||||
|
|
||||||
|
@@ -7,19 +7,6 @@ namespace binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "binary_sensor";
|
static const char *const TAG = "binary_sensor";
|
||||||
|
|
||||||
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
|
|
||||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
|
|
||||||
if (obj == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
|
||||||
|
|
||||||
if (!obj->get_device_class_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BinarySensor::publish_state(bool new_state) {
|
void BinarySensor::publish_state(bool new_state) {
|
||||||
if (this->filter_list_ == nullptr) {
|
if (this->filter_list_ == nullptr) {
|
||||||
this->send_state_internal(new_state);
|
this->send_state_internal(new_state);
|
||||||
|
@@ -10,10 +10,13 @@ namespace esphome {
|
|||||||
|
|
||||||
namespace binary_sensor {
|
namespace binary_sensor {
|
||||||
|
|
||||||
class BinarySensor;
|
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
||||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
if ((obj) != nullptr) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
if (!(obj)->get_device_class().empty()) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define SUB_BINARY_SENSOR(name) \
|
#define SUB_BINARY_SENSOR(name) \
|
||||||
protected: \
|
protected: \
|
||||||
|
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
cg.add(
|
cg.add(
|
||||||
@@ -63,6 +63,6 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
cg.add(var.set_char_uuid128(uuid128))
|
cg.add(var.set_char_uuid128(uuid128))
|
||||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||||
await output.register_output(var, config)
|
yield output.register_output(var, config)
|
||||||
await ble_client.register_ble_node(var, config)
|
yield ble_client.register_ble_node(var, config)
|
||||||
await cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
|
@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
||||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
||||||
cv.only_with_esp_idf, cv.boolean
|
cv.only_with_esp_idf, cv.boolean
|
||||||
),
|
),
|
||||||
|
@@ -375,19 +375,10 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
|
this->reset_connection_(param->disconnect.reason);
|
||||||
// This prevents race condition where we mark slot as free before controller cleanup is complete
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
|
|
||||||
param->disconnect.reason);
|
|
||||||
// Send disconnection notification but don't free the slot yet
|
|
||||||
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CLOSE_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
|
|
||||||
param->close.reason);
|
|
||||||
// Now the GATT connection is fully closed and controller resources are freed
|
|
||||||
// Safe to mark the connection slot as available
|
|
||||||
this->reset_connection_(param->close.reason);
|
this->reset_connection_(param->close.reason);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
class BluetoothProxy;
|
class BluetoothProxy;
|
||||||
|
|
||||||
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
@@ -24,9 +24,6 @@ void BluetoothProxy::setup() {
|
|||||||
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
|
|
||||||
// Capture the configured scan mode from YAML before any API changes
|
|
||||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
|
||||||
|
|
||||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||||
if (this->api_connection_ != nullptr) {
|
if (this->api_connection_ != nullptr) {
|
||||||
this->send_bluetooth_scanner_state_(state);
|
this->send_bluetooth_scanner_state_(state);
|
||||||
@@ -39,9 +36,6 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
|||||||
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
||||||
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||||
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
||||||
resp.configured_mode = this->configured_scan_active_
|
|
||||||
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
|
||||||
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
|
||||||
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,12 +183,6 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
this->send_device_connection(msg.address, false);
|
this->send_device_connection(msg.address, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!msg.has_address_type) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
|
|
||||||
connection->address_str().c_str());
|
|
||||||
this->send_device_connection(msg.address, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (connection->state() == espbt::ClientState::CONNECTED ||
|
if (connection->state() == espbt::ClientState::CONNECTED ||
|
||||||
connection->state() == espbt::ClientState::ESTABLISHED) {
|
connection->state() == espbt::ClientState::ESTABLISHED) {
|
||||||
this->log_connection_request_ignored_(connection, connection->state());
|
this->log_connection_request_ignored_(connection, connection->state());
|
||||||
@@ -221,9 +209,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
||||||
this->log_connection_info_(connection, "v3 without cache");
|
this->log_connection_info_(connection, "v3 without cache");
|
||||||
}
|
}
|
||||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
if (msg.has_address_type) {
|
||||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||||
|
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||||
|
} else {
|
||||||
|
connection->set_state(espbt::ClientState::SEARCHING);
|
||||||
|
}
|
||||||
this->send_connections_free();
|
this->send_connections_free();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
|||||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||||
public:
|
public:
|
||||||
BluetoothProxy();
|
BluetoothProxy();
|
||||||
@@ -161,8 +161,7 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
|
|||||||
// Group 4: 1-byte types grouped together
|
// Group 4: 1-byte types grouped together
|
||||||
bool active_;
|
bool active_;
|
||||||
uint8_t connection_count_{0};
|
uint8_t connection_count_{0};
|
||||||
bool configured_scan_active_{false}; // Configured scan mode from YAML
|
// 2 bytes used, 2 bytes padding
|
||||||
// 3 bytes used, 1 byte padding
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
|
|||||||
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
|
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
|
||||||
uint8_t raw_data[len * 2];
|
uint8_t raw_data[len * 2];
|
||||||
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
|
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
|
||||||
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
|
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@@ -63,12 +63,12 @@ void BMP280Component::setup() {
|
|||||||
|
|
||||||
// Read the chip id twice, to work around a bug where the first read is 0.
|
// Read the chip id twice, to work around a bug where the first read is 0.
|
||||||
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
||||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
if (!this->read_byte(0xD0, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
if (!this->read_byte(0xD0, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||||
return;
|
return;
|
||||||
@@ -80,7 +80,7 @@ void BMP280Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send a soft reset.
|
// Send a soft reset.
|
||||||
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||||
this->mark_failed("Reset failed");
|
this->mark_failed("Reset failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ void BMP280Component::setup() {
|
|||||||
uint8_t retry = 5;
|
uint8_t retry = 5;
|
||||||
do {
|
do {
|
||||||
delay(2);
|
delay(2);
|
||||||
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
|
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||||
this->mark_failed("Error reading status register");
|
this->mark_failed("Error reading status register");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -115,14 +115,14 @@ void BMP280Component::setup() {
|
|||||||
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
||||||
|
|
||||||
uint8_t config_register = 0;
|
uint8_t config_register = 0;
|
||||||
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||||
this->mark_failed("Read config");
|
this->mark_failed("Read config");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config_register &= ~0b11111100;
|
config_register &= ~0b11111100;
|
||||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||||
this->mark_failed("Write config");
|
this->mark_failed("Write config");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ void BMP280Component::update() {
|
|||||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||||
meas_value |= 0b01; // Forced mode
|
meas_value |= 0b01; // Forced mode
|
||||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
|
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -188,10 +188,9 @@ void BMP280Component::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||||
uint8_t data[3]{};
|
uint8_t data[3];
|
||||||
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||||
return NAN;
|
return NAN;
|
||||||
ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
|
|
||||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||||
adc >>= 4;
|
adc >>= 4;
|
||||||
if (adc == 0x80000) {
|
if (adc == 0x80000) {
|
||||||
@@ -213,7 +212,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
|||||||
|
|
||||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||||
uint8_t data[3];
|
uint8_t data[3];
|
||||||
if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
||||||
return NAN;
|
return NAN;
|
||||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||||
adc >>= 4;
|
adc >>= 4;
|
||||||
@@ -259,12 +258,12 @@ void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over
|
|||||||
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||||
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
||||||
uint8_t data = 0;
|
uint8_t data = 0;
|
||||||
this->bmp_read_byte(a_register, &data);
|
this->read_byte(a_register, &data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
||||||
uint16_t data = 0;
|
uint16_t data = 0;
|
||||||
this->bmp_read_byte_16(a_register, &data);
|
this->read_byte_16(a_register, &data);
|
||||||
return (data >> 8) | (data << 8);
|
return (data >> 8) | (data << 8);
|
||||||
}
|
}
|
||||||
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||||
|
@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
|
|||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
protected:
|
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
|
||||||
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
|
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
|
||||||
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
|
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
|
||||||
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
|
|
||||||
|
|
||||||
|
protected:
|
||||||
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||||
float read_temperature_(int32_t *t_fine);
|
float read_temperature_(int32_t *t_fine);
|
||||||
/// Read the pressure value in hPa using the provided t_fine value.
|
/// Read the pressure value in hPa using the provided t_fine value.
|
||||||
|
@@ -5,6 +5,19 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bmp280_i2c {
|
namespace bmp280_i2c {
|
||||||
|
|
||||||
|
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||||
|
return I2CDevice::read_byte(a_register, data);
|
||||||
|
};
|
||||||
|
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||||
|
return I2CDevice::write_byte(a_register, data);
|
||||||
|
};
|
||||||
|
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||||
|
return I2CDevice::read_bytes(a_register, data, len);
|
||||||
|
};
|
||||||
|
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||||
|
return I2CDevice::read_byte_16(a_register, data);
|
||||||
|
};
|
||||||
|
|
||||||
void BMP280I2CComponent::dump_config() {
|
void BMP280I2CComponent::dump_config() {
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
BMP280Component::dump_config();
|
BMP280Component::dump_config();
|
||||||
|
@@ -11,12 +11,10 @@ static const char *const TAG = "bmp280_i2c.sensor";
|
|||||||
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
||||||
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||||
return read_bytes(a_register, data, len);
|
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||||
}
|
|
||||||
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
|
|||||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
|
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
|
||||||
|
|
||||||
bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
|
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||||
this->enable();
|
this->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
*data = this->transfer_byte(0);
|
*data = this->transfer_byte(0);
|
||||||
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
|
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||||
this->enable();
|
this->enable();
|
||||||
this->transfer_byte(clear_bit(a_register, 7));
|
this->transfer_byte(clear_bit(a_register, 7));
|
||||||
this->transfer_byte(data);
|
this->transfer_byte(data);
|
||||||
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||||
this->enable();
|
this->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
this->read_array(data, len);
|
this->read_array(data, len);
|
||||||
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
|
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||||
this->enable();
|
this->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
((uint8_t *) data)[1] = this->transfer_byte(0);
|
((uint8_t *) data)[1] = this->transfer_byte(0);
|
||||||
|
@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
|
|||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||||
void setup() override;
|
void setup() override;
|
||||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
|
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||||
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
|
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bmp280_spi
|
} // namespace bmp280_spi
|
||||||
|
@@ -17,7 +17,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
DEVICE_CLASS_UPDATE,
|
DEVICE_CLASS_UPDATE,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -134,6 +134,6 @@ async def button_press_to_code(config, action_id, template_arg, args):
|
|||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(button_ns.using)
|
cg.add_global(button_ns.using)
|
||||||
|
@@ -6,19 +6,6 @@ namespace button {
|
|||||||
|
|
||||||
static const char *const TAG = "button";
|
static const char *const TAG = "button";
|
||||||
|
|
||||||
// Function implementation of LOG_BUTTON macro to reduce code size
|
|
||||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj) {
|
|
||||||
if (obj == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
|
||||||
|
|
||||||
if (!obj->get_icon_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::press() {
|
void Button::press() {
|
||||||
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
||||||
this->press_action();
|
this->press_action();
|
||||||
|
@@ -7,10 +7,13 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace button {
|
namespace button {
|
||||||
|
|
||||||
class Button;
|
#define LOG_BUTTON(prefix, type, obj) \
|
||||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
|
if ((obj) != nullptr) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
if (!(obj)->get_icon().empty()) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define SUB_BUTTON(name) \
|
#define SUB_BUTTON(name) \
|
||||||
protected: \
|
protected: \
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
namespace esphome::camera {
|
|
||||||
|
|
||||||
/// Interface for a generic buffer that stores image data.
|
|
||||||
class Buffer {
|
|
||||||
public:
|
|
||||||
/// Returns a pointer to the buffer's data.
|
|
||||||
virtual uint8_t *get_data_buffer() = 0;
|
|
||||||
/// Returns the length of the buffer in bytes.
|
|
||||||
virtual size_t get_data_length() = 0;
|
|
||||||
virtual ~Buffer() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::camera
|
|
@@ -1,20 +0,0 @@
|
|||||||
#include "buffer_impl.h"
|
|
||||||
|
|
||||||
namespace esphome::camera {
|
|
||||||
|
|
||||||
BufferImpl::BufferImpl(size_t size) {
|
|
||||||
this->data_ = this->allocator_.allocate(size);
|
|
||||||
this->size_ = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferImpl::BufferImpl(CameraImageSpec *spec) {
|
|
||||||
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
|
|
||||||
this->size_ = spec->bytes_per_image();
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferImpl::~BufferImpl() {
|
|
||||||
if (this->data_ != nullptr)
|
|
||||||
this->allocator_.deallocate(this->data_, this->size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::camera
|
|
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "buffer.h"
|
|
||||||
#include "camera.h"
|
|
||||||
|
|
||||||
namespace esphome::camera {
|
|
||||||
|
|
||||||
/// Default implementation of Buffer Interface.
|
|
||||||
/// Uses a RAMAllocator for memory reservation.
|
|
||||||
class BufferImpl : public Buffer {
|
|
||||||
public:
|
|
||||||
explicit BufferImpl(size_t size);
|
|
||||||
explicit BufferImpl(CameraImageSpec *spec);
|
|
||||||
// -------- Buffer --------
|
|
||||||
uint8_t *get_data_buffer() override { return data_; }
|
|
||||||
size_t get_data_length() override { return size_; }
|
|
||||||
// ------------------------
|
|
||||||
~BufferImpl() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
RAMAllocator<uint8_t> allocator_;
|
|
||||||
size_t size_{};
|
|
||||||
uint8_t *data_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::camera
|
|
@@ -15,26 +15,6 @@ namespace camera {
|
|||||||
*/
|
*/
|
||||||
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
|
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
|
||||||
|
|
||||||
/// Enumeration of different pixel formats.
|
|
||||||
enum PixelFormat : uint8_t {
|
|
||||||
PIXEL_FORMAT_GRAYSCALE = 0, ///< 8-bit grayscale.
|
|
||||||
PIXEL_FORMAT_RGB565, ///< 16-bit RGB (5-6-5).
|
|
||||||
PIXEL_FORMAT_BGR888, ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each).
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns string name for a given PixelFormat.
|
|
||||||
inline const char *to_string(PixelFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case PIXEL_FORMAT_GRAYSCALE:
|
|
||||||
return "PIXEL_FORMAT_GRAYSCALE";
|
|
||||||
case PIXEL_FORMAT_RGB565:
|
|
||||||
return "PIXEL_FORMAT_RGB565";
|
|
||||||
case PIXEL_FORMAT_BGR888:
|
|
||||||
return "PIXEL_FORMAT_BGR888";
|
|
||||||
}
|
|
||||||
return "PIXEL_FORMAT_UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Abstract camera image base class.
|
/** Abstract camera image base class.
|
||||||
* Encapsulates the JPEG encoded data and it is shared among
|
* Encapsulates the JPEG encoded data and it is shared among
|
||||||
* all connected clients.
|
* all connected clients.
|
||||||
@@ -63,29 +43,6 @@ class CameraImageReader {
|
|||||||
virtual ~CameraImageReader() {}
|
virtual ~CameraImageReader() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Specification of a caputured camera image.
|
|
||||||
/// This struct defines the format and size details for images captured
|
|
||||||
/// or processed by a camera component.
|
|
||||||
struct CameraImageSpec {
|
|
||||||
uint16_t width;
|
|
||||||
uint16_t height;
|
|
||||||
PixelFormat format;
|
|
||||||
size_t bytes_per_pixel() {
|
|
||||||
switch (format) {
|
|
||||||
case PIXEL_FORMAT_GRAYSCALE:
|
|
||||||
return 1;
|
|
||||||
case PIXEL_FORMAT_RGB565:
|
|
||||||
return 2;
|
|
||||||
case PIXEL_FORMAT_BGR888:
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
size_t bytes_per_row() { return bytes_per_pixel() * width; }
|
|
||||||
size_t bytes_per_image() { return bytes_per_pixel() * width * height; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Abstract camera base class. Collaborates with API.
|
/** Abstract camera base class. Collaborates with API.
|
||||||
* 1) API server starts and installs callback (add_image_callback)
|
* 1) API server starts and installs callback (add_image_callback)
|
||||||
* which is called by the camera when a new image is available.
|
* which is called by the camera when a new image is available.
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "buffer.h"
|
|
||||||
#include "camera.h"
|
|
||||||
|
|
||||||
namespace esphome::camera {
|
|
||||||
|
|
||||||
/// Result codes from the encoder used to control camera pipeline flow.
|
|
||||||
enum EncoderError : uint8_t {
|
|
||||||
ENCODER_ERROR_SUCCESS = 0, ///< Encoding succeeded, continue pipeline normally.
|
|
||||||
ENCODER_ERROR_SKIP_FRAME, ///< Skip current frame, try again on next frame.
|
|
||||||
ENCODER_ERROR_RETRY_FRAME, ///< Retry current frame, after buffer growth or for incremental encoding.
|
|
||||||
ENCODER_ERROR_CONFIGURATION ///< Fatal config error, shut down pipeline.
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Converts EncoderError to string.
|
|
||||||
inline const char *to_string(EncoderError error) {
|
|
||||||
switch (error) {
|
|
||||||
case ENCODER_ERROR_SUCCESS:
|
|
||||||
return "ENCODER_ERROR_SUCCESS";
|
|
||||||
case ENCODER_ERROR_SKIP_FRAME:
|
|
||||||
return "ENCODER_ERROR_SKIP_FRAME";
|
|
||||||
case ENCODER_ERROR_RETRY_FRAME:
|
|
||||||
return "ENCODER_ERROR_RETRY_FRAME";
|
|
||||||
case ENCODER_ERROR_CONFIGURATION:
|
|
||||||
return "ENCODER_ERROR_CONFIGURATION";
|
|
||||||
}
|
|
||||||
return "ENCODER_ERROR_INVALID";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interface for an encoder buffer supporting resizing and variable-length data.
|
|
||||||
class EncoderBuffer {
|
|
||||||
public:
|
|
||||||
/// Sets logical buffer size, reallocates if needed.
|
|
||||||
/// @param size Required size in bytes.
|
|
||||||
/// @return true on success, false on allocation failure.
|
|
||||||
virtual bool set_buffer_size(size_t size) = 0;
|
|
||||||
|
|
||||||
/// Returns a pointer to the buffer data.
|
|
||||||
virtual uint8_t *get_data() const = 0;
|
|
||||||
|
|
||||||
/// Returns number of bytes currently used.
|
|
||||||
virtual size_t get_size() const = 0;
|
|
||||||
|
|
||||||
/// Returns total allocated buffer size.
|
|
||||||
virtual size_t get_max_size() const = 0;
|
|
||||||
|
|
||||||
virtual ~EncoderBuffer() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Interface for image encoders used in a camera pipeline.
|
|
||||||
class Encoder {
|
|
||||||
public:
|
|
||||||
/// Encodes pixel data from a previous camera pipeline stage.
|
|
||||||
/// @param spec Specification of the input pixel data.
|
|
||||||
/// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec.
|
|
||||||
/// @return EncoderError Indicating the result of the encoding operation.
|
|
||||||
virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0;
|
|
||||||
|
|
||||||
/// Returns the encoder's output buffer.
|
|
||||||
/// @return Pointer to an EncoderBuffer containing encoded data.
|
|
||||||
virtual EncoderBuffer *get_output_buffer() = 0;
|
|
||||||
|
|
||||||
/// Prints the encoder's configuration to the log.
|
|
||||||
virtual void dump_config() = 0;
|
|
||||||
virtual ~Encoder() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::camera
|
|
@@ -1,62 +0,0 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components.esp32 import add_idf_component
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE
|
|
||||||
from esphome.core import CORE
|
|
||||||
from esphome.types import ConfigType
|
|
||||||
|
|
||||||
CODEOWNERS = ["@DT-art1"]
|
|
||||||
|
|
||||||
AUTO_LOAD = ["camera"]
|
|
||||||
|
|
||||||
CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size"
|
|
||||||
CONF_ENCODER_BUFFER_ID = "encoder_buffer_id"
|
|
||||||
CONF_QUALITY = "quality"
|
|
||||||
|
|
||||||
ESP32_CAMERA_ENCODER = "esp32_camera"
|
|
||||||
|
|
||||||
camera_ns = cg.esphome_ns.namespace("camera")
|
|
||||||
camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder")
|
|
||||||
|
|
||||||
Encoder = camera_ns.class_("Encoder")
|
|
||||||
EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl")
|
|
||||||
|
|
||||||
ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder)
|
|
||||||
|
|
||||||
MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024
|
|
||||||
|
|
||||||
ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder),
|
|
||||||
cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100),
|
|
||||||
cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range(
|
|
||||||
1024, MAX_JPEG_BUFFER_SIZE_2MB
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range(
|
|
||||||
0, MAX_JPEG_BUFFER_SIZE_2MB
|
|
||||||
),
|
|
||||||
cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.typed_schema(
|
|
||||||
{
|
|
||||||
ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA,
|
|
||||||
},
|
|
||||||
default_type=ESP32_CAMERA_ENCODER,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
|
||||||
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
|
|
||||||
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
|
||||||
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
|
|
||||||
if CORE.using_esp_idf:
|
|
||||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.0")
|
|
||||||
cg.add_build_flag("-DUSE_ESP32_CAMERA_JPEG_ENCODER")
|
|
||||||
var = cg.new_Pvariable(
|
|
||||||
config[CONF_ID],
|
|
||||||
config[CONF_QUALITY],
|
|
||||||
buffer,
|
|
||||||
)
|
|
||||||
cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE]))
|
|
@@ -1,23 +0,0 @@
|
|||||||
#include "encoder_buffer_impl.h"
|
|
||||||
|
|
||||||
namespace esphome::camera_encoder {
|
|
||||||
|
|
||||||
bool EncoderBufferImpl::set_buffer_size(size_t size) {
|
|
||||||
if (size > this->capacity_) {
|
|
||||||
uint8_t *p = this->allocator_.reallocate(this->data_, size);
|
|
||||||
if (p == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this->data_ = p;
|
|
||||||
this->capacity_ = size;
|
|
||||||
}
|
|
||||||
this->size_ = size;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
EncoderBufferImpl::~EncoderBufferImpl() {
|
|
||||||
if (this->data_ != nullptr)
|
|
||||||
this->allocator_.deallocate(this->data_, this->capacity_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::camera_encoder
|
|
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/components/camera/encoder.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
|
|
||||||
namespace esphome::camera_encoder {
|
|
||||||
|
|
||||||
class EncoderBufferImpl : public camera::EncoderBuffer {
|
|
||||||
public:
|
|
||||||
// --- EncoderBuffer ---
|
|
||||||
bool set_buffer_size(size_t size) override;
|
|
||||||
uint8_t *get_data() const override { return this->data_; }
|
|
||||||
size_t get_size() const override { return this->size_; }
|
|
||||||
size_t get_max_size() const override { return this->capacity_; }
|
|
||||||
// ----------------------
|
|
||||||
~EncoderBufferImpl() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
RAMAllocator<uint8_t> allocator_;
|
|
||||||
size_t capacity_{};
|
|
||||||
size_t size_{};
|
|
||||||
uint8_t *data_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::camera_encoder
|
|
@@ -1,82 +0,0 @@
|
|||||||
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
|
|
||||||
|
|
||||||
#include "esp32_camera_jpeg_encoder.h"
|
|
||||||
|
|
||||||
namespace esphome::camera_encoder {
|
|
||||||
|
|
||||||
static const char *const TAG = "camera_encoder";
|
|
||||||
|
|
||||||
ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) {
|
|
||||||
this->quality_ = quality;
|
|
||||||
this->output_ = output;
|
|
||||||
}
|
|
||||||
|
|
||||||
camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) {
|
|
||||||
this->bytes_written_ = 0;
|
|
||||||
this->out_of_output_memory_ = false;
|
|
||||||
bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height,
|
|
||||||
to_internal_(spec->format), this->quality_, callback_, this);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
return camera::ENCODER_ERROR_CONFIGURATION;
|
|
||||||
|
|
||||||
if (this->out_of_output_memory_) {
|
|
||||||
if (this->buffer_expand_size_ <= 0)
|
|
||||||
return camera::ENCODER_ERROR_SKIP_FRAME;
|
|
||||||
|
|
||||||
size_t current_size = this->output_->get_max_size();
|
|
||||||
size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_;
|
|
||||||
if (!this->output_->set_buffer_size(new_size)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to expand output buffer.");
|
|
||||||
this->buffer_expand_size_ = 0;
|
|
||||||
return camera::ENCODER_ERROR_SKIP_FRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size());
|
|
||||||
return camera::ENCODER_ERROR_RETRY_FRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->output_->set_buffer_size(this->bytes_written_);
|
|
||||||
return camera::ENCODER_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32CameraJPEGEncoder::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
"ESP32 Camera JPEG Encoder:\n"
|
|
||||||
" Size: %zu\n"
|
|
||||||
" Quality: %d\n"
|
|
||||||
" Expand: %d\n",
|
|
||||||
this->output_->get_max_size(), this->quality_, this->buffer_expand_size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ESP32CameraJPEGEncoder::callback_(void *arg, size_t index, const void *data, size_t len) {
|
|
||||||
ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg);
|
|
||||||
uint8_t *buffer = that->output_->get_data();
|
|
||||||
size_t buffer_length = that->output_->get_max_size();
|
|
||||||
if (index + len > buffer_length) {
|
|
||||||
that->out_of_output_memory_ = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(&buffer[index], data, len);
|
|
||||||
that->bytes_written_ += len;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case camera::PIXEL_FORMAT_GRAYSCALE:
|
|
||||||
return PIXFORMAT_GRAYSCALE;
|
|
||||||
case camera::PIXEL_FORMAT_RGB565:
|
|
||||||
return PIXFORMAT_RGB565;
|
|
||||||
// Internal representation for RGB is in byte order: B, G, R
|
|
||||||
case camera::PIXEL_FORMAT_BGR888:
|
|
||||||
return PIXFORMAT_RGB888;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PIXFORMAT_GRAYSCALE;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::camera_encoder
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,39 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
|
|
||||||
|
|
||||||
#include <esp_camera.h>
|
|
||||||
|
|
||||||
#include "esphome/components/camera/encoder.h"
|
|
||||||
|
|
||||||
namespace esphome::camera_encoder {
|
|
||||||
|
|
||||||
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
|
|
||||||
class ESP32CameraJPEGEncoder : public camera::Encoder {
|
|
||||||
public:
|
|
||||||
/// Constructs a ESP32CameraJPEGEncoder instance.
|
|
||||||
/// @param quality Sets the quality of the encoded image (1-100).
|
|
||||||
/// @param output Pointer to preallocated output buffer.
|
|
||||||
ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output);
|
|
||||||
/// Sets the number of bytes to expand the output buffer on underflow during encoding.
|
|
||||||
/// @param buffer_expand_size Number of bytes to expand the buffer.
|
|
||||||
void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; }
|
|
||||||
// -------- Encoder --------
|
|
||||||
camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override;
|
|
||||||
camera::EncoderBuffer *get_output_buffer() override { return output_; }
|
|
||||||
void dump_config() override;
|
|
||||||
// -------------------------
|
|
||||||
protected:
|
|
||||||
static size_t callback_(void *arg, size_t index, const void *data, size_t len);
|
|
||||||
pixformat_t to_internal_(camera::PixelFormat format);
|
|
||||||
|
|
||||||
camera::EncoderBuffer *output_{};
|
|
||||||
size_t buffer_expand_size_{};
|
|
||||||
size_t bytes_written_{};
|
|
||||||
uint8_t quality_{};
|
|
||||||
bool out_of_output_memory_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::camera_encoder
|
|
||||||
|
|
||||||
#endif
|
|
@@ -10,7 +10,7 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||||
DEPENDENCIES = ["wifi"]
|
DEPENDENCIES = ["wifi"]
|
||||||
@@ -40,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
@coroutine_with_priority(64.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
|
|
||||||
|
@@ -7,83 +7,103 @@ namespace esphome {
|
|||||||
namespace captive_portal {
|
namespace captive_portal {
|
||||||
|
|
||||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x6d, 0x6f, 0xdb, 0x38, 0x12, 0xfe, 0xde,
|
||||||
0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5,
|
0x5f, 0x31, 0xa7, 0x36, 0x6b, 0x6b, 0x1b, 0x51, 0x22, 0xe5, 0xb7, 0xd8, 0x92, 0x16, 0x69, 0xae, 0x8b, 0x5d, 0xa0,
|
||||||
0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38,
|
0xdd, 0x2d, 0x90, 0x6c, 0xef, 0x43, 0x51, 0x20, 0xb4, 0x34, 0xb2, 0xd8, 0x48, 0xa4, 0x4e, 0xa4, 0x5f, 0x52, 0xc3,
|
||||||
0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b,
|
0xf7, 0xdb, 0x0f, 0x94, 0x6c, 0xc7, 0xe9, 0x35, 0x87, 0xeb, 0xe2, 0x0e, 0x87, 0xdd, 0x18, 0x21, 0x86, 0xe4, 0xcc,
|
||||||
0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e,
|
0x70, 0xe6, 0xf1, 0x0c, 0x67, 0xcc, 0xe8, 0x2f, 0x99, 0x4a, 0xcd, 0x7d, 0x8d, 0x50, 0x98, 0xaa, 0x4c, 0x22, 0x3b,
|
||||||
0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b,
|
0x42, 0xc9, 0xe5, 0x22, 0x46, 0x99, 0x44, 0x05, 0xf2, 0x2c, 0x89, 0x2a, 0x34, 0x1c, 0xd2, 0x82, 0x37, 0x1a, 0x4d,
|
||||||
0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11,
|
0xfc, 0xdb, 0xcd, 0x8f, 0xde, 0x04, 0xfc, 0x24, 0x2a, 0x85, 0xbc, 0x83, 0x06, 0xcb, 0x58, 0xa4, 0x4a, 0x42, 0xd1,
|
||||||
0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92,
|
0x60, 0x1e, 0x67, 0xdc, 0xf0, 0xa9, 0xa8, 0xf8, 0x02, 0x2d, 0x43, 0x2b, 0x26, 0x79, 0x85, 0xf1, 0x4a, 0xe0, 0xba,
|
||||||
0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9,
|
0x56, 0x8d, 0x81, 0x54, 0x49, 0x83, 0xd2, 0xc4, 0xce, 0x5a, 0x64, 0xa6, 0x88, 0x33, 0x5c, 0x89, 0x14, 0xbd, 0x76,
|
||||||
0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2,
|
0x72, 0x2e, 0xa4, 0x30, 0x82, 0x97, 0x9e, 0x4e, 0x79, 0x89, 0x31, 0x3d, 0x5f, 0x6a, 0x6c, 0xda, 0x09, 0x9f, 0x97,
|
||||||
0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7,
|
0x18, 0x4b, 0xe5, 0xf8, 0x49, 0xa4, 0xd3, 0x46, 0xd4, 0x06, 0xac, 0xbd, 0x71, 0xa5, 0xb2, 0x65, 0x89, 0x89, 0xef,
|
||||||
0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59,
|
0x73, 0xad, 0xd1, 0x68, 0x5f, 0xc8, 0x0c, 0x37, 0x64, 0x14, 0x86, 0x29, 0xe3, 0xe3, 0x9c, 0x7c, 0xd2, 0xcf, 0x32,
|
||||||
0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9,
|
0x95, 0x2e, 0x2b, 0x94, 0x86, 0x94, 0x2a, 0xe5, 0x46, 0x28, 0x49, 0x34, 0xf2, 0x26, 0x2d, 0xe2, 0x38, 0x76, 0x7e,
|
||||||
0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf,
|
0xd0, 0x7c, 0x85, 0xce, 0x77, 0xdf, 0xf5, 0x8f, 0x4c, 0x0b, 0x34, 0xaf, 0x4b, 0xb4, 0xa4, 0x7e, 0x75, 0x7f, 0xc3,
|
||||||
0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5,
|
0x17, 0xbf, 0xf0, 0x0a, 0xfb, 0x0e, 0xd7, 0x22, 0x43, 0xc7, 0xfd, 0x10, 0x7c, 0x24, 0xda, 0xdc, 0x97, 0x48, 0x32,
|
||||||
0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1,
|
0xa1, 0xeb, 0x92, 0xdf, 0xc7, 0xce, 0xbc, 0x54, 0xe9, 0x9d, 0xe3, 0xce, 0xf2, 0xa5, 0x4c, 0xad, 0x72, 0xd0, 0x7d,
|
||||||
0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98,
|
0x74, 0xb7, 0x25, 0x1a, 0x30, 0xf1, 0x5b, 0x6e, 0x0a, 0x52, 0xf1, 0x4d, 0xbf, 0x23, 0x84, 0xec, 0xb3, 0xef, 0xfb,
|
||||||
0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e,
|
0xf8, 0x92, 0x06, 0x81, 0x7b, 0xde, 0x0e, 0x81, 0xeb, 0xd3, 0x20, 0x98, 0x35, 0x68, 0x96, 0x8d, 0x04, 0xde, 0xbf,
|
||||||
0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a,
|
0x8d, 0x6a, 0x6e, 0x0a, 0xc8, 0x62, 0xa7, 0xa2, 0x8c, 0x04, 0xc1, 0x04, 0xe8, 0x05, 0x61, 0x43, 0x8f, 0x52, 0x12,
|
||||||
0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24,
|
0x7a, 0x74, 0x98, 0x8e, 0xbd, 0x21, 0xd0, 0x81, 0x37, 0x04, 0xc6, 0xc8, 0x10, 0x82, 0xcf, 0x0e, 0xe4, 0xa2, 0x2c,
|
||||||
0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a,
|
0x63, 0x47, 0x2a, 0x89, 0x0e, 0x68, 0xd3, 0xa8, 0x3b, 0x8c, 0x9d, 0x74, 0xd9, 0x34, 0x28, 0xcd, 0x95, 0x2a, 0x55,
|
||||||
0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76,
|
0xe3, 0xf8, 0xc9, 0x33, 0x78, 0xf4, 0xf7, 0xcd, 0x47, 0x98, 0x86, 0x4b, 0x9d, 0xab, 0xa6, 0x8a, 0x9d, 0xf6, 0x4b,
|
||||||
0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4,
|
0xe9, 0xbf, 0xd8, 0x9a, 0x1d, 0xd8, 0xc1, 0x3d, 0xd9, 0xf4, 0x54, 0x23, 0x16, 0x42, 0xc6, 0x0e, 0x65, 0x40, 0x27,
|
||||||
0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07,
|
0x8e, 0x9f, 0xdc, 0xba, 0xbb, 0x23, 0x26, 0xdc, 0x62, 0xb2, 0xf7, 0x52, 0xf5, 0x3f, 0xdc, 0x46, 0x7a, 0xb5, 0x80,
|
||||||
0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c,
|
0x4d, 0x55, 0x4a, 0x1d, 0x3b, 0x85, 0x31, 0xf5, 0xd4, 0xf7, 0xd7, 0xeb, 0x35, 0x59, 0x87, 0x44, 0x35, 0x0b, 0x9f,
|
||||||
0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42,
|
0x05, 0x41, 0xe0, 0xeb, 0xd5, 0xc2, 0x81, 0x2e, 0x3e, 0x1c, 0x36, 0x70, 0xa0, 0x40, 0xb1, 0x28, 0x4c, 0x4b, 0x27,
|
||||||
0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf,
|
0x2f, 0xb6, 0xb8, 0x8b, 0x2c, 0x47, 0x72, 0xfb, 0xf1, 0xe4, 0x14, 0x71, 0x72, 0x0a, 0xfe, 0x70, 0x82, 0x66, 0xef,
|
||||||
0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c,
|
0xad, 0x35, 0x6a, 0xcc, 0x19, 0x30, 0x08, 0xda, 0x0f, 0xf3, 0x2c, 0xbd, 0x9f, 0x79, 0x5f, 0xcc, 0xe0, 0x64, 0x06,
|
||||||
0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47,
|
0x0c, 0x9e, 0x01, 0xb0, 0x6a, 0xe4, 0x5d, 0x1c, 0xc5, 0xa9, 0xdd, 0x5e, 0xd1, 0xe0, 0x61, 0xc1, 0xca, 0xfc, 0x34,
|
||||||
0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7,
|
0x3a, 0x9d, 0x7b, 0xec, 0xbd, 0x65, 0xb0, 0xd8, 0x1f, 0x85, 0x3c, 0x56, 0xd0, 0xf7, 0x23, 0x3e, 0x84, 0xe1, 0x7e,
|
||||||
0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06,
|
0x65, 0xe8, 0x59, 0xfa, 0x38, 0xb3, 0x27, 0xc1, 0x70, 0xc5, 0x0a, 0x5a, 0x79, 0x23, 0x6f, 0xc8, 0x43, 0x08, 0xf7,
|
||||||
0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89,
|
0x26, 0x85, 0x10, 0xae, 0x58, 0x31, 0x7a, 0x3f, 0x3a, 0x5d, 0xf3, 0xc2, 0xcf, 0x3d, 0x0b, 0xf3, 0xd4, 0x71, 0x1e,
|
||||||
0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0,
|
0x30, 0x50, 0xa7, 0x18, 0x90, 0x4f, 0x4a, 0xc8, 0xbe, 0xe3, 0xb8, 0xbb, 0x1c, 0x4d, 0x5a, 0xf4, 0x1d, 0x3f, 0x55,
|
||||||
0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0,
|
0x32, 0x17, 0x0b, 0xf2, 0x49, 0x2b, 0xe9, 0xb8, 0xc4, 0x14, 0x28, 0xfb, 0x07, 0x51, 0x2b, 0x88, 0xed, 0x4e, 0xff,
|
||||||
0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75,
|
0xcb, 0x1d, 0xe3, 0x6e, 0x8f, 0xf9, 0x61, 0x84, 0x29, 0x31, 0x36, 0xc4, 0x66, 0xf4, 0xf9, 0x71, 0x75, 0xae, 0xb2,
|
||||||
0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a,
|
0xfb, 0x27, 0x52, 0xa7, 0xa0, 0x5d, 0xde, 0x08, 0x29, 0xb1, 0xb9, 0xc1, 0x8d, 0x89, 0x9d, 0xb7, 0x97, 0x57, 0x70,
|
||||||
0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8,
|
0x99, 0x65, 0x0d, 0x6a, 0x3d, 0x05, 0xe7, 0xa5, 0x21, 0x15, 0x4f, 0xff, 0x73, 0x5d, 0xf4, 0x91, 0xae, 0xbf, 0x89,
|
||||||
0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32,
|
0x1f, 0x05, 0xfc, 0x82, 0x66, 0xad, 0x9a, 0xbb, 0xbd, 0x36, 0x6b, 0xda, 0xcc, 0x66, 0x60, 0x13, 0x1b, 0xc2, 0x6b,
|
||||||
0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60,
|
0x4d, 0x74, 0x29, 0x52, 0xec, 0x53, 0x97, 0x54, 0xbc, 0x7e, 0xf0, 0x4a, 0x1e, 0x80, 0xba, 0x8d, 0x32, 0xb1, 0x82,
|
||||||
0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b,
|
0xb4, 0xe4, 0x5a, 0xc7, 0x8e, 0xec, 0x54, 0x39, 0xb0, 0x4f, 0x1b, 0x25, 0xd3, 0x52, 0xa4, 0x77, 0xb1, 0xf3, 0x95,
|
||||||
0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11,
|
0x1b, 0xe2, 0xd5, 0xfd, 0xcf, 0x59, 0xbf, 0xa7, 0xb5, 0xc8, 0x7a, 0x2e, 0x59, 0xf1, 0x72, 0x89, 0x10, 0x83, 0x29,
|
||||||
0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94,
|
0x84, 0x7e, 0x30, 0x70, 0xf6, 0xa4, 0x58, 0xad, 0xef, 0x7a, 0x2e, 0xc9, 0x55, 0xba, 0xd4, 0x7d, 0xd7, 0x39, 0x64,
|
||||||
0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f,
|
0x69, 0xc4, 0xbb, 0x3b, 0xd4, 0x79, 0xee, 0x7c, 0x61, 0x91, 0x57, 0x62, 0x6e, 0x9c, 0x87, 0x6c, 0x7e, 0xb1, 0xd5,
|
||||||
0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32,
|
0x7d, 0x49, 0x1a, 0xad, 0x85, 0xbb, 0x3b, 0x2e, 0x46, 0xba, 0xe6, 0xf2, 0x4b, 0x41, 0x6b, 0xa0, 0x4d, 0x1a, 0x49,
|
||||||
0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3,
|
0x2c, 0x65, 0x33, 0xa7, 0xe6, 0xf2, 0x78, 0xa0, 0xcf, 0x0f, 0xe4, 0x8b, 0xad, 0xe8, 0x4b, 0x7b, 0x4b, 0xde, 0x1d,
|
||||||
0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7,
|
0x35, 0x46, 0x7e, 0x26, 0x56, 0xc9, 0xed, 0xce, 0x7d, 0xf0, 0xe3, 0xef, 0x4b, 0x6c, 0xee, 0xaf, 0xb1, 0xc4, 0xd4,
|
||||||
0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04,
|
0xa8, 0xa6, 0xef, 0x3c, 0x97, 0x68, 0x1c, 0xb7, 0x73, 0xf8, 0xa7, 0x9b, 0xb7, 0x6f, 0x62, 0xd5, 0x6f, 0xdc, 0xf3,
|
||||||
0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3,
|
0xa7, 0xb8, 0x6d, 0xb5, 0xf8, 0xd0, 0x60, 0xf9, 0x8f, 0xb8, 0x67, 0xeb, 0x45, 0xef, 0xa3, 0xe3, 0x92, 0xd6, 0xdf,
|
||||||
0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb,
|
0xdb, 0x87, 0xa2, 0x61, 0x13, 0xfb, 0xe5, 0xa6, 0x2a, 0xcf, 0xad, 0x87, 0xde, 0x68, 0xe8, 0xee, 0x6e, 0x77, 0xee,
|
||||||
0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f,
|
0xce, 0x9d, 0x45, 0x7e, 0x77, 0xef, 0x27, 0x51, 0x7b, 0x05, 0x27, 0xdf, 0x6f, 0xe7, 0x6a, 0xe3, 0x69, 0xf1, 0x59,
|
||||||
0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79,
|
0xc8, 0xc5, 0x54, 0xc8, 0x02, 0x1b, 0x61, 0x76, 0x99, 0x58, 0x9d, 0x0b, 0x59, 0x2f, 0xcd, 0xb6, 0xe6, 0x59, 0x66,
|
||||||
0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19,
|
0x77, 0x86, 0xf5, 0x66, 0x96, 0x2b, 0x69, 0x2c, 0x27, 0x4e, 0x29, 0x56, 0xbb, 0x6e, 0xbf, 0xbd, 0x5b, 0xa6, 0x17,
|
||||||
0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b,
|
0xc3, 0xb3, 0x9d, 0x0d, 0xb8, 0xad, 0xc1, 0x8d, 0xf1, 0x78, 0x29, 0x16, 0x72, 0x9a, 0xa2, 0x34, 0xd8, 0x74, 0x42,
|
||||||
0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43,
|
0x39, 0xaf, 0x44, 0x79, 0x3f, 0xd5, 0x5c, 0x6a, 0x4f, 0x63, 0x23, 0xf2, 0xdd, 0x7c, 0x69, 0x8c, 0x92, 0xdb, 0xb9,
|
||||||
0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c,
|
0x6a, 0x32, 0x6c, 0xa6, 0xc1, 0xac, 0x23, 0xbc, 0x86, 0x67, 0x62, 0xa9, 0xa7, 0x24, 0x6c, 0xb0, 0x9a, 0xcd, 0x79,
|
||||||
0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2,
|
0x7a, 0xb7, 0x68, 0xd4, 0x52, 0x66, 0x5e, 0x6a, 0x6f, 0xe1, 0xe9, 0x73, 0x9a, 0xf3, 0x10, 0xd3, 0xd9, 0x7e, 0x96,
|
||||||
0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1,
|
0xe7, 0xf9, 0xac, 0x14, 0x12, 0xbd, 0xee, 0x56, 0x9b, 0x32, 0x32, 0xb0, 0x62, 0x27, 0x66, 0x12, 0x66, 0x17, 0x3a,
|
||||||
0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6,
|
0x1b, 0x69, 0x10, 0x9c, 0xcd, 0x0e, 0xee, 0x04, 0xb3, 0x74, 0xd9, 0x68, 0xd5, 0x4c, 0x6b, 0x25, 0xac, 0x99, 0xbb,
|
||||||
0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a,
|
0x8a, 0x0b, 0x79, 0x6a, 0xbd, 0x0d, 0x93, 0xd9, 0xbe, 0x3c, 0x4d, 0x85, 0x6c, 0x8f, 0x69, 0x8b, 0xd4, 0xac, 0x12,
|
||||||
0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67,
|
0xb2, 0x2b, 0xb2, 0x53, 0x36, 0x0a, 0xea, 0xcd, 0x8e, 0xec, 0x03, 0x64, 0x7b, 0xe0, 0xce, 0x4b, 0xdc, 0xcc, 0x3e,
|
||||||
0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17,
|
0x2d, 0xb5, 0x11, 0xf9, 0xbd, 0xb7, 0x2f, 0xd2, 0x53, 0x5d, 0xf3, 0x14, 0xbd, 0x39, 0x9a, 0x35, 0xa2, 0x9c, 0xb5,
|
||||||
0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3,
|
0x67, 0x78, 0xc2, 0x60, 0xa5, 0xf7, 0x38, 0x1d, 0xd5, 0xb4, 0x01, 0xfa, 0x58, 0xd7, 0xbf, 0xe3, 0xb6, 0xb1, 0xb8,
|
||||||
0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63,
|
0xad, 0x78, 0xb3, 0x10, 0xd2, 0x9b, 0x2b, 0x63, 0x54, 0x35, 0xf5, 0xc6, 0xf5, 0x66, 0xb6, 0x5f, 0xb2, 0xca, 0xa6,
|
||||||
0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80,
|
0xd4, 0x9a, 0xd9, 0xd6, 0xde, 0x03, 0xde, 0xb4, 0xde, 0x80, 0x56, 0xa5, 0xc8, 0xf6, 0x7c, 0x2d, 0x0b, 0x04, 0x47,
|
||||||
0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87,
|
0x78, 0xe8, 0xb0, 0xde, 0x80, 0x5d, 0x3b, 0x40, 0x3d, 0xc8, 0x27, 0x9c, 0x06, 0x5f, 0xf9, 0x46, 0xb2, 0x3c, 0x67,
|
||||||
0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3,
|
0xf3, 0xfc, 0x88, 0x94, 0x2d, 0xa1, 0x3b, 0xb1, 0x8f, 0x0a, 0x36, 0xa8, 0x37, 0xb3, 0xc3, 0x77, 0x33, 0xa8, 0x37,
|
||||||
0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43,
|
0x3b, 0xd1, 0xa6, 0xc5, 0xf6, 0x44, 0x4b, 0x1b, 0xaa, 0xd3, 0x65, 0x53, 0xf6, 0x9d, 0xaf, 0x84, 0xee, 0x59, 0x78,
|
||||||
0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b,
|
0xf5, 0x50, 0xe2, 0x7a, 0x4f, 0x97, 0xb8, 0x1e, 0xd8, 0xa6, 0xe8, 0x95, 0xda, 0xc4, 0xbd, 0xb6, 0xd8, 0x0c, 0x80,
|
||||||
0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8,
|
0x0d, 0x7a, 0x67, 0xe1, 0xeb, 0xb3, 0xf0, 0xea, 0xbf, 0x52, 0xbb, 0x7e, 0x77, 0xe1, 0xfa, 0x86, 0xaa, 0xf5, 0x8d,
|
||||||
0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf,
|
0x15, 0xab, 0xf3, 0xce, 0x3a, 0x7f, 0x16, 0xbe, 0x76, 0xdc, 0x9d, 0x20, 0x5a, 0x2c, 0xe8, 0xff, 0x02, 0xda, 0x7f,
|
||||||
0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf,
|
0xc5, 0x31, 0xbc, 0xa4, 0x13, 0x72, 0x01, 0xed, 0xd0, 0x41, 0x44, 0xc2, 0x09, 0x8c, 0xaf, 0x06, 0x64, 0x40, 0xc1,
|
||||||
0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f,
|
0xb6, 0x43, 0x23, 0x18, 0x93, 0xc9, 0x05, 0xd0, 0x11, 0x09, 0xc7, 0x40, 0x19, 0x30, 0x4a, 0x86, 0x6f, 0x58, 0x48,
|
||||||
0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00,
|
0x46, 0x43, 0x18, 0x5f, 0xb1, 0x80, 0x84, 0x0c, 0x3a, 0xde, 0x11, 0x61, 0x0c, 0x42, 0xcb, 0x12, 0x56, 0x01, 0xb0,
|
||||||
0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44,
|
0x34, 0x24, 0xc1, 0x18, 0x02, 0x18, 0x91, 0xe0, 0x82, 0x4c, 0x46, 0x30, 0x21, 0x63, 0x0a, 0x8c, 0x0c, 0x86, 0xa5,
|
||||||
0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73,
|
0x37, 0x24, 0x14, 0x46, 0x24, 0x1c, 0xf1, 0x09, 0x19, 0x84, 0xd0, 0x0e, 0x1d, 0x1c, 0x63, 0xc2, 0x98, 0x47, 0x02,
|
||||||
0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe,
|
0xfa, 0x26, 0x24, 0x6c, 0x0c, 0x63, 0x32, 0x18, 0x5c, 0xd2, 0x11, 0xb9, 0x18, 0x40, 0x37, 0x76, 0xf0, 0x52, 0x06,
|
||||||
0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
0xc3, 0xa7, 0x40, 0x63, 0x7f, 0x5e, 0xd0, 0x42, 0xc2, 0x28, 0x84, 0xe4, 0x62, 0xc2, 0x6d, 0x5f, 0xca, 0xa0, 0x1b,
|
||||||
|
0x3b, 0xdc, 0x28, 0x85, 0xe0, 0x77, 0x63, 0x16, 0xfe, 0x79, 0x31, 0xa3, 0x16, 0x01, 0x46, 0x06, 0xe1, 0x25, 0x0d,
|
||||||
|
0xc9, 0x08, 0xda, 0xa1, 0x3b, 0x9b, 0x32, 0x98, 0x5c, 0x5d, 0xc0, 0x04, 0x46, 0x64, 0x34, 0x81, 0x0b, 0x18, 0x5a,
|
||||||
|
0x74, 0x2f, 0xc8, 0x64, 0xd0, 0x09, 0x79, 0x8c, 0x7c, 0x2b, 0x8c, 0x83, 0x3f, 0x30, 0x8c, 0x4f, 0xf9, 0xf4, 0x07,
|
||||||
|
0x76, 0xe9, 0xff, 0x71, 0x05, 0x45, 0x7e, 0xd7, 0x86, 0x45, 0x7e, 0xf7, 0x3c, 0x60, 0xbb, 0xa8, 0x24, 0xb2, 0xdd,
|
||||||
|
0x48, 0x12, 0x15, 0x14, 0x44, 0x16, 0x57, 0x3c, 0x4d, 0x4e, 0x5a, 0xfd, 0xc8, 0x2f, 0xe8, 0x61, 0xab, 0xa0, 0xc9,
|
||||||
|
0xa3, 0xc6, 0xbd, 0xdb, 0x6b, 0x2b, 0x7d, 0x72, 0x53, 0x20, 0xbc, 0xbe, 0x7e, 0x07, 0x6b, 0x51, 0x96, 0x20, 0xd5,
|
||||||
|
0x1a, 0x4c, 0x73, 0x0f, 0x46, 0xd9, 0x57, 0x03, 0x89, 0xa9, 0xb1, 0xa4, 0x29, 0x10, 0xf6, 0x7d, 0x04, 0x21, 0x24,
|
||||||
|
0x9a, 0x37, 0xc9, 0xbb, 0x12, 0xb9, 0x46, 0x58, 0x88, 0x15, 0x82, 0x30, 0xa0, 0x55, 0x85, 0x60, 0x84, 0x1d, 0x8e,
|
||||||
|
0x82, 0x2d, 0x5f, 0xe4, 0x77, 0x87, 0x74, 0x8d, 0xb2, 0xc8, 0x62, 0x89, 0x26, 0xd9, 0x77, 0xc4, 0x51, 0x11, 0x76,
|
||||||
|
0x56, 0x5d, 0xa3, 0x31, 0x42, 0x2e, 0xac, 0x55, 0x61, 0x12, 0xd9, 0x5f, 0xb7, 0xc0, 0xdb, 0xdf, 0x0c, 0xb1, 0xbf,
|
||||||
|
0x16, 0xb9, 0xb0, 0x6f, 0x06, 0x49, 0xd4, 0x76, 0x91, 0x56, 0x83, 0x6d, 0x64, 0xba, 0x07, 0x8e, 0x96, 0x2a, 0x51,
|
||||||
|
0x2e, 0x4c, 0x11, 0x87, 0x0c, 0xea, 0x92, 0xa7, 0x58, 0xa8, 0x32, 0xc3, 0x26, 0xbe, 0xbe, 0xfe, 0xf9, 0xaf, 0xf6,
|
||||||
|
0x35, 0xc4, 0x9a, 0x70, 0x94, 0xac, 0xf5, 0x5d, 0x27, 0x68, 0x89, 0xbd, 0xdc, 0x68, 0xd0, 0xbd, 0x6b, 0xd4, 0x5c,
|
||||||
|
0xeb, 0xb5, 0x6a, 0xb2, 0x47, 0x5a, 0xde, 0x1d, 0x16, 0xf7, 0x9a, 0xda, 0xff, 0xb6, 0x1f, 0xed, 0x84, 0xf4, 0x72,
|
||||||
|
0x5e, 0x09, 0x93, 0x5c, 0xf3, 0x15, 0x46, 0x7e, 0xb7, 0x91, 0x44, 0xbe, 0x75, 0xa0, 0xe3, 0x2d, 0xf6, 0x32, 0x05,
|
||||||
|
0x4d, 0x7e, 0xbd, 0xb9, 0x84, 0xdf, 0xea, 0x8c, 0x1b, 0xec, 0xb0, 0x6f, 0xbd, 0xac, 0xd0, 0x14, 0x2a, 0x8b, 0xdf,
|
||||||
|
0xfd, 0x7a, 0x7d, 0x73, 0xf4, 0x78, 0xd9, 0x32, 0x01, 0xca, 0xb4, 0x7b, 0x6f, 0x59, 0x96, 0x46, 0xd4, 0xbc, 0x31,
|
||||||
|
0xad, 0x5a, 0xcf, 0x66, 0xc7, 0xc1, 0xa3, 0x76, 0x3f, 0x17, 0x25, 0x76, 0x4e, 0xed, 0x05, 0xfd, 0x04, 0xbe, 0x66,
|
||||||
|
0xe3, 0xe1, 0xec, 0x2f, 0xac, 0xf4, 0xbb, 0x00, 0xf2, 0xbb, 0x68, 0xf2, 0xdb, 0xd7, 0xa8, 0x7f, 0x02, 0x14, 0xee,
|
||||||
|
0xbc, 0x64, 0x9d, 0x12, 0x00, 0x00};
|
||||||
|
|
||||||
} // namespace captive_portal
|
} // namespace captive_portal
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, "CCS811");
|
ESP_LOGCONFIG(TAG, "CCS811");
|
||||||
LOG_I2C_DEVICE(this)
|
LOG_I2C_DEVICE(this)
|
||||||
LOG_UPDATE_INTERVAL(this)
|
LOG_UPDATE_INTERVAL(this)
|
||||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
|
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
|
||||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
|
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
|
||||||
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
||||||
if (this->baseline_) {
|
if (this->baseline_) {
|
||||||
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
||||||
|
@@ -91,7 +91,7 @@ bool CH422GComponent::read_inputs_() {
|
|||||||
|
|
||||||
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
|
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
|
||||||
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||||
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
|
auto err = this->bus_->write(reg, &value, 1);
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
||||||
return false;
|
return false;
|
||||||
@@ -102,7 +102,7 @@ bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
|||||||
|
|
||||||
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
||||||
uint8_t value;
|
uint8_t value;
|
||||||
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
|
auto err = this->bus_->read(reg, &value, 1);
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -47,7 +47,7 @@ from esphome.const import (
|
|||||||
CONF_VISUAL,
|
CONF_VISUAL,
|
||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -517,6 +517,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(climate_ns.using)
|
cg.add_global(climate_ns.using)
|
||||||
|
@@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
|
|||||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||||
|
|
||||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||||
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^
|
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^
|
||||||
RESTORE_STATE_VERSION);
|
RESTORE_STATE_VERSION);
|
||||||
ClimateDeviceRestoreState recovered{};
|
ClimateDeviceRestoreState recovered{};
|
||||||
if (!this->rtc_.load(&recovered))
|
if (!this->rtc_.load(&recovered))
|
||||||
|
@@ -32,7 +32,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_SHUTTER,
|
DEVICE_CLASS_SHUTTER,
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
|
|
||||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
||||||
async def cover_toggle_to_code(config, action_id, template_arg, args):
|
def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = yield cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||||
@@ -263,6 +263,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(cover_ns.using)
|
cg.add_global(cover_ns.using)
|
||||||
|
@@ -194,7 +194,7 @@ void Cover::publish_state(bool save) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
optional<CoverRestoreState> Cover::restore_state_() {
|
optional<CoverRestoreState> Cover::restore_state_() {
|
||||||
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash());
|
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||||
CoverRestoreState recovered{};
|
CoverRestoreState recovered{};
|
||||||
if (!this->rtc_.load(&recovered))
|
if (!this->rtc_.load(&recovered))
|
||||||
return {};
|
return {};
|
||||||
|
@@ -19,8 +19,8 @@ const extern float COVER_CLOSED;
|
|||||||
if (traits_.get_is_assumed_state()) { \
|
if (traits_.get_is_assumed_state()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||||
} \
|
} \
|
||||||
if (!(obj)->get_device_class_ref().empty()) { \
|
if (!(obj)->get_device_class().empty()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ from esphome.const import (
|
|||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
CONF_YEAR,
|
CONF_YEAR,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ async def new_datetime(config, *args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(datetime_ns.using)
|
cg.add_global(datetime_ns.using)
|
||||||
|
|
||||||
|
@@ -16,8 +16,8 @@ namespace datetime {
|
|||||||
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
if (!(obj)->get_icon().empty()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,8 +16,8 @@ namespace datetime {
|
|||||||
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
if (!(obj)->get_icon().empty()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,8 +16,8 @@ namespace datetime {
|
|||||||
#define LOG_DATETIME_TIME(prefix, type, obj) \
|
#define LOG_DATETIME_TIME(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
if (!(obj)->get_icon().empty()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "soc/soc_caps.h"
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "deep_sleep_component.h"
|
#include "deep_sleep_component.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -84,11 +83,7 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
}
|
}
|
||||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||||
gpio_hold_en(gpio_pin);
|
gpio_hold_en(gpio_pin);
|
||||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
|
||||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
|
||||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
|
||||||
gpio_deep_sleep_hold_en();
|
gpio_deep_sleep_hold_en();
|
||||||
#endif
|
|
||||||
bool level = !this->wakeup_pin_->is_inverted();
|
bool level = !this->wakeup_pin_->is_inverted();
|
||||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||||
level = !level;
|
level = !level;
|
||||||
@@ -125,11 +120,7 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
}
|
}
|
||||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||||
gpio_hold_en(gpio_pin);
|
gpio_hold_en(gpio_pin);
|
||||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
|
||||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
|
||||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
|
||||||
gpio_deep_sleep_hold_en();
|
gpio_deep_sleep_hold_en();
|
||||||
#endif
|
|
||||||
bool level = !this->wakeup_pin_->is_inverted();
|
bool level = !this->wakeup_pin_->is_inverted();
|
||||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||||
level = !level;
|
level = !level;
|
||||||
|
@@ -15,7 +15,7 @@ from esphome.const import (
|
|||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
SCHEDULER_DONT_RUN,
|
SCHEDULER_DONT_RUN,
|
||||||
)
|
)
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
|
|||||||
DisplayPageShowNextAction,
|
DisplayPageShowNextAction,
|
||||||
maybe_simple_id(
|
maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
|
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -190,7 +190,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args):
|
|||||||
DisplayPageShowPrevAction,
|
DisplayPageShowPrevAction,
|
||||||
maybe_simple_id(
|
maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
|
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -218,7 +218,7 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.CORE)
|
@coroutine_with_priority(100.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(display_ns.using)
|
cg.add_global(display_ns.using)
|
||||||
cg.add_define("USE_DISPLAY")
|
cg.add_define("USE_DISPLAY")
|
||||||
|
@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
|
|||||||
uint32_t seconds = 0;
|
uint32_t seconds = 0;
|
||||||
|
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash());
|
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash());
|
||||||
this->pref_.load(&seconds);
|
this->pref_.load(&seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
|
|||||||
crc16 = calc_crc16_(address, 6);
|
crc16 = calc_crc16_(address, 6);
|
||||||
address[5] = crc16 & 0xFF;
|
address[5] = crc16 & 0xFF;
|
||||||
address[6] = (crc16 >> 8) & 0xFF;
|
address[6] = (crc16 >> 8) & 0xFF;
|
||||||
this->write(address, 7);
|
this->write(address, 7, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
float EE895Component::read_float_() {
|
float EE895Component::read_float_() {
|
||||||
|
@@ -40,7 +40,6 @@ from esphome.cpp_generator import RawExpression
|
|||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
|
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
from esphome.writer import clean_cmake_cache
|
|
||||||
|
|
||||||
from .boards import BOARDS, STANDARD_BOARDS
|
from .boards import BOARDS, STANDARD_BOARDS
|
||||||
from .const import ( # noqa
|
from .const import ( # noqa
|
||||||
@@ -825,9 +824,8 @@ async def to_code(config):
|
|||||||
cg.set_cpp_standard("gnu++20")
|
cg.set_cpp_standard("gnu++20")
|
||||||
cg.add_build_flag("-DUSE_ESP32")
|
cg.add_build_flag("-DUSE_ESP32")
|
||||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||||
variant = config[CONF_VARIANT]
|
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
|
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
|
|
||||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||||
|
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
@@ -841,9 +839,6 @@ async def to_code(config):
|
|||||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
||||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
||||||
|
|
||||||
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
|
|
||||||
os.environ.pop(clean_var, None)
|
|
||||||
|
|
||||||
add_extra_script(
|
add_extra_script(
|
||||||
"post",
|
"post",
|
||||||
"post_build.py",
|
"post_build.py",
|
||||||
@@ -859,7 +854,11 @@ async def to_code(config):
|
|||||||
|
|
||||||
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
|
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
|
||||||
|
|
||||||
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
|
||||||
|
# This is espressif's own published version which is more up to date.
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
|
||||||
|
)
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
||||||
)
|
)
|
||||||
@@ -1078,11 +1077,7 @@ def _write_idf_component_yml():
|
|||||||
contents = yaml_util.dump({"dependencies": dependencies})
|
contents = yaml_util.dump({"dependencies": dependencies})
|
||||||
else:
|
else:
|
||||||
contents = ""
|
contents = ""
|
||||||
if write_file_if_changed(yml_path, contents):
|
write_file_if_changed(yml_path, contents)
|
||||||
dependencies_lock = CORE.relative_build_path("dependencies.lock")
|
|
||||||
if os.path.isfile(dependencies_lock):
|
|
||||||
os.remove(dependencies_lock)
|
|
||||||
clean_cmake_cache()
|
|
||||||
|
|
||||||
|
|
||||||
# Called by writer.py
|
# Called by writer.py
|
||||||
|
@@ -54,13 +54,13 @@ struct ISRPinArg {
|
|||||||
|
|
||||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
|
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
|
||||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
arg->pin = this->get_pin_num();
|
arg->pin = this->pin_;
|
||||||
arg->flags = gpio::FLAG_NONE;
|
arg->flags = gpio::FLAG_NONE;
|
||||||
arg->inverted = pin_flags_.inverted;
|
arg->inverted = inverted_;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num());
|
arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_);
|
||||||
if (arg->use_rtc)
|
if (arg->use_rtc)
|
||||||
arg->rtc_pin = rtc_io_number_get(this->get_pin_num());
|
arg->rtc_pin = rtc_io_number_get(this->pin_);
|
||||||
#endif
|
#endif
|
||||||
return ISRInternalGPIOPin((void *) arg);
|
return ISRInternalGPIOPin((void *) arg);
|
||||||
}
|
}
|
||||||
@@ -69,23 +69,23 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
|||||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case gpio::INTERRUPT_RISING_EDGE:
|
case gpio::INTERRUPT_RISING_EDGE:
|
||||||
idf_type = pin_flags_.inverted ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||||
break;
|
break;
|
||||||
case gpio::INTERRUPT_FALLING_EDGE:
|
case gpio::INTERRUPT_FALLING_EDGE:
|
||||||
idf_type = pin_flags_.inverted ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||||
break;
|
break;
|
||||||
case gpio::INTERRUPT_ANY_EDGE:
|
case gpio::INTERRUPT_ANY_EDGE:
|
||||||
idf_type = GPIO_INTR_ANYEDGE;
|
idf_type = GPIO_INTR_ANYEDGE;
|
||||||
break;
|
break;
|
||||||
case gpio::INTERRUPT_LOW_LEVEL:
|
case gpio::INTERRUPT_LOW_LEVEL:
|
||||||
idf_type = pin_flags_.inverted ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||||
break;
|
break;
|
||||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||||
idf_type = pin_flags_.inverted ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
gpio_set_intr_type(get_pin_num(), idf_type);
|
gpio_set_intr_type(pin_, idf_type);
|
||||||
gpio_intr_enable(get_pin_num());
|
gpio_intr_enable(pin_);
|
||||||
if (!isr_service_installed) {
|
if (!isr_service_installed) {
|
||||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||||
if (res != ESP_OK) {
|
if (res != ESP_OK) {
|
||||||
@@ -94,7 +94,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
|||||||
}
|
}
|
||||||
isr_service_installed = true;
|
isr_service_installed = true;
|
||||||
}
|
}
|
||||||
gpio_isr_handler_add(get_pin_num(), func, arg);
|
gpio_isr_handler_add(pin_, func, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ESP32InternalGPIOPin::dump_summary() const {
|
std::string ESP32InternalGPIOPin::dump_summary() const {
|
||||||
@@ -112,13 +112,13 @@ void ESP32InternalGPIOPin::setup() {
|
|||||||
conf.intr_type = GPIO_INTR_DISABLE;
|
conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
gpio_config(&conf);
|
gpio_config(&conf);
|
||||||
if (flags_ & gpio::FLAG_OUTPUT) {
|
if (flags_ & gpio::FLAG_OUTPUT) {
|
||||||
gpio_set_drive_capability(get_pin_num(), get_drive_strength());
|
gpio_set_drive_capability(pin_, drive_strength_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||||
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
||||||
gpio_set_direction(get_pin_num(), flags_to_mode(flags));
|
gpio_set_direction(pin_, flags_to_mode(flags));
|
||||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||||
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
|
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
|
||||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||||
@@ -127,14 +127,12 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
|||||||
} else if (flags & gpio::FLAG_PULLDOWN) {
|
} else if (flags & gpio::FLAG_PULLDOWN) {
|
||||||
pull_mode = GPIO_PULLDOWN_ONLY;
|
pull_mode = GPIO_PULLDOWN_ONLY;
|
||||||
}
|
}
|
||||||
gpio_set_pull_mode(get_pin_num(), pull_mode);
|
gpio_set_pull_mode(pin_, pull_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(get_pin_num())) != pin_flags_.inverted; }
|
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||||
void ESP32InternalGPIOPin::digital_write(bool value) {
|
void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||||
gpio_set_level(get_pin_num(), value != pin_flags_.inverted ? 1 : 0);
|
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
|
||||||
}
|
|
||||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(get_pin_num()); }
|
|
||||||
|
|
||||||
} // namespace esp32
|
} // namespace esp32
|
||||||
|
|
||||||
|
@@ -7,17 +7,11 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32 {
|
namespace esp32 {
|
||||||
|
|
||||||
// Static assertions to ensure our bit-packed fields can hold the enum values
|
|
||||||
static_assert(GPIO_NUM_MAX <= 256, "gpio_num_t has too many values for uint8_t");
|
|
||||||
static_assert(GPIO_DRIVE_CAP_MAX <= 4, "gpio_drive_cap_t has too many values for 2-bit field");
|
|
||||||
|
|
||||||
class ESP32InternalGPIOPin : public InternalGPIOPin {
|
class ESP32InternalGPIOPin : public InternalGPIOPin {
|
||||||
public:
|
public:
|
||||||
void set_pin(gpio_num_t pin) { pin_ = static_cast<uint8_t>(pin); }
|
void set_pin(gpio_num_t pin) { pin_ = pin; }
|
||||||
void set_inverted(bool inverted) { pin_flags_.inverted = inverted; }
|
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||||
void set_drive_strength(gpio_drive_cap_t drive_strength) {
|
void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; }
|
||||||
pin_flags_.drive_strength = static_cast<uint8_t>(drive_strength);
|
|
||||||
}
|
|
||||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
@@ -27,26 +21,17 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
|
|||||||
std::string dump_summary() const override;
|
std::string dump_summary() const override;
|
||||||
void detach_interrupt() const override;
|
void detach_interrupt() const override;
|
||||||
ISRInternalGPIOPin to_isr() const override;
|
ISRInternalGPIOPin to_isr() const override;
|
||||||
uint8_t get_pin() const override { return pin_; }
|
uint8_t get_pin() const override { return (uint8_t) pin_; }
|
||||||
gpio::Flags get_flags() const override { return flags_; }
|
gpio::Flags get_flags() const override { return flags_; }
|
||||||
bool is_inverted() const override { return pin_flags_.inverted; }
|
bool is_inverted() const override { return inverted_; }
|
||||||
gpio_num_t get_pin_num() const { return static_cast<gpio_num_t>(pin_); }
|
|
||||||
gpio_drive_cap_t get_drive_strength() const { return static_cast<gpio_drive_cap_t>(pin_flags_.drive_strength); }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||||
|
|
||||||
// Memory layout: 8 bytes total on 32-bit systems
|
gpio_num_t pin_;
|
||||||
// - 3 bytes for members below
|
gpio_drive_cap_t drive_strength_;
|
||||||
// - 1 byte padding for alignment
|
gpio::Flags flags_;
|
||||||
// - 4 bytes for vtable pointer
|
bool inverted_;
|
||||||
uint8_t pin_; // GPIO pin number (0-255, actual max ~48 on ESP32)
|
|
||||||
gpio::Flags flags_; // GPIO flags (1 byte)
|
|
||||||
struct PinFlags {
|
|
||||||
uint8_t inverted : 1; // Invert pin logic (1 bit)
|
|
||||||
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
|
|
||||||
uint8_t reserved : 5; // Reserved for future use (5 bits)
|
|
||||||
} pin_flags_; // Total: 1 byte
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
static bool isr_service_installed;
|
static bool isr_service_installed;
|
||||||
};
|
};
|
||||||
|
@@ -5,14 +5,9 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
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 CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
|
||||||
CONF_ENABLE_ON_BOOT,
|
|
||||||
CONF_ESPHOME,
|
|
||||||
CONF_ID,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_NAME_ADD_MAC_SUFFIX,
|
|
||||||
)
|
|
||||||
from esphome.core import CORE, TimePeriod
|
from esphome.core import CORE, TimePeriod
|
||||||
|
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
@@ -285,10 +280,6 @@ async def to_code(config):
|
|||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
|
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
|
||||||
)
|
)
|
||||||
# Increase GATT client connection retry count for problematic devices
|
|
||||||
# Default in ESP-IDF is 3, we increase to 10 for better reliability with
|
|
||||||
# low-power/timing-sensitive devices
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
|
|
||||||
|
|
||||||
# Set the maximum number of notification registrations
|
# Set the maximum number of notification registrations
|
||||||
# This controls how many BLE characteristics can have notifications enabled
|
# This controls how many BLE characteristics can have notifications enabled
|
||||||
|
@@ -306,7 +306,7 @@ void ESP32BLE::loop() {
|
|||||||
case BLEEvent::GATTS: {
|
case BLEEvent::GATTS: {
|
||||||
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
||||||
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
|
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
|
||||||
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
|
esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param;
|
||||||
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||||
@@ -316,7 +316,7 @@ void ESP32BLE::loop() {
|
|||||||
case BLEEvent::GATTC: {
|
case BLEEvent::GATTC: {
|
||||||
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
|
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
|
||||||
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
|
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
|
||||||
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
|
esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param;
|
||||||
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||||
|
@@ -61,24 +61,10 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(es
|
|||||||
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
|
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
|
||||||
"remote_addr must follow rssi in read_rssi_cmpl");
|
"remote_addr must follow rssi in read_rssi_cmpl");
|
||||||
|
|
||||||
// Param struct sizes on ESP32
|
|
||||||
static constexpr size_t GATTC_PARAM_SIZE = 28;
|
|
||||||
static constexpr size_t GATTS_PARAM_SIZE = 32;
|
|
||||||
|
|
||||||
// Maximum size for inline storage of data
|
|
||||||
// GATTC: 80 - 28 (param) - 8 (other fields) = 44 bytes for data
|
|
||||||
// GATTS: 80 - 32 (param) - 8 (other fields) = 40 bytes for data
|
|
||||||
static constexpr size_t GATTC_INLINE_DATA_SIZE = 44;
|
|
||||||
static constexpr size_t GATTS_INLINE_DATA_SIZE = 40;
|
|
||||||
|
|
||||||
// Verify param struct sizes
|
|
||||||
static_assert(sizeof(esp_ble_gattc_cb_param_t) == GATTC_PARAM_SIZE, "GATTC param size unexpected");
|
|
||||||
static_assert(sizeof(esp_ble_gatts_cb_param_t) == GATTS_PARAM_SIZE, "GATTS param size unexpected");
|
|
||||||
|
|
||||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||||
// This class stores each event with minimal memory usage.
|
// This class stores each event with minimal memory usage.
|
||||||
// GAP events (99% of traffic) don't have the heap allocation overhead.
|
// GAP events (99% of traffic) don't have the heap allocation overhead.
|
||||||
// GATTC/GATTS events use heap allocation for their param and inline storage for small data.
|
// GATTC/GATTS events use heap allocation for their param and data.
|
||||||
//
|
//
|
||||||
// Event flow:
|
// Event flow:
|
||||||
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
|
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
|
||||||
@@ -125,21 +111,21 @@ class BLEEvent {
|
|||||||
this->init_gap_data_(e, p);
|
this->init_gap_data_(e, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor for GATTC events - param stored inline, data may use heap
|
// Constructor for GATTC events - uses heap allocation
|
||||||
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
|
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||||
// is only valid during the callback execution. Since BLE events are processed
|
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||||
// asynchronously in the main loop, we store our own copy inline to ensure
|
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||||
// the data remains valid until the event is processed.
|
// our own copy to ensure the data remains valid until the event is processed.
|
||||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||||
this->type_ = GATTC;
|
this->type_ = GATTC;
|
||||||
this->init_gattc_data_(e, i, p);
|
this->init_gattc_data_(e, i, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor for GATTS events - param stored inline, data may use heap
|
// Constructor for GATTS events - uses heap allocation
|
||||||
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
|
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||||
// is only valid during the callback execution. Since BLE events are processed
|
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||||
// asynchronously in the main loop, we store our own copy inline to ensure
|
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||||
// the data remains valid until the event is processed.
|
// our own copy to ensure the data remains valid until the event is processed.
|
||||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||||
this->type_ = GATTS;
|
this->type_ = GATTS;
|
||||||
this->init_gatts_data_(e, i, p);
|
this->init_gatts_data_(e, i, p);
|
||||||
@@ -149,32 +135,27 @@ class BLEEvent {
|
|||||||
~BLEEvent() { this->release(); }
|
~BLEEvent() { this->release(); }
|
||||||
|
|
||||||
// Default constructor for pre-allocation in pool
|
// Default constructor for pre-allocation in pool
|
||||||
BLEEvent() : event_{}, type_(GAP) {}
|
BLEEvent() : type_(GAP) {}
|
||||||
|
|
||||||
// Invoked on return to EventPool - clean up any heap-allocated data
|
// Invoked on return to EventPool - clean up any heap-allocated data
|
||||||
void release() {
|
void release() {
|
||||||
switch (this->type_) {
|
if (this->type_ == GAP) {
|
||||||
case GAP:
|
return;
|
||||||
// GAP events don't have heap allocations
|
}
|
||||||
break;
|
if (this->type_ == GATTC) {
|
||||||
case GATTC:
|
delete this->event_.gattc.gattc_param;
|
||||||
// Param is now stored inline, only delete heap data if it was heap-allocated
|
delete[] this->event_.gattc.data;
|
||||||
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
delete[] this->event_.gattc.data.heap_data;
|
this->event_.gattc.data = nullptr;
|
||||||
}
|
this->event_.gattc.data_len = 0;
|
||||||
// Clear critical fields to prevent issues if type changes
|
return;
|
||||||
this->event_.gattc.is_inline = false;
|
}
|
||||||
this->event_.gattc.data.heap_data = nullptr;
|
if (this->type_ == GATTS) {
|
||||||
break;
|
delete this->event_.gatts.gatts_param;
|
||||||
case GATTS:
|
delete[] this->event_.gatts.data;
|
||||||
// Param is now stored inline, only delete heap data if it was heap-allocated
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
|
this->event_.gatts.data = nullptr;
|
||||||
delete[] this->event_.gatts.data.heap_data;
|
this->event_.gatts.data_len = 0;
|
||||||
}
|
|
||||||
// Clear critical fields to prevent issues if type changes
|
|
||||||
this->event_.gatts.is_inline = false;
|
|
||||||
this->event_.gatts.data.heap_data = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,30 +207,22 @@ class BLEEvent {
|
|||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
struct gattc_event {
|
struct gattc_event {
|
||||||
esp_ble_gattc_cb_param_t gattc_param; // Stored inline (28 bytes)
|
esp_gattc_cb_event_t gattc_event;
|
||||||
esp_gattc_cb_event_t gattc_event; // 4 bytes
|
esp_gatt_if_t gattc_if;
|
||||||
union {
|
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
||||||
uint8_t *heap_data; // 4 bytes when heap-allocated
|
uint8_t *data; // Heap-allocated raw buffer (manually managed)
|
||||||
uint8_t inline_data[GATTC_INLINE_DATA_SIZE]; // 44 bytes when stored inline
|
uint16_t data_len; // Track size separately
|
||||||
} data; // 44 bytes total
|
} gattc;
|
||||||
uint16_t data_len; // 2 bytes
|
|
||||||
esp_gatt_if_t gattc_if; // 1 byte
|
|
||||||
bool is_inline; // 1 byte - true when data is stored inline
|
|
||||||
} gattc; // Total: 80 bytes
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
struct gatts_event {
|
struct gatts_event {
|
||||||
esp_ble_gatts_cb_param_t gatts_param; // Stored inline (32 bytes)
|
esp_gatts_cb_event_t gatts_event;
|
||||||
esp_gatts_cb_event_t gatts_event; // 4 bytes
|
esp_gatt_if_t gatts_if;
|
||||||
union {
|
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
||||||
uint8_t *heap_data; // 4 bytes when heap-allocated
|
uint8_t *data; // Heap-allocated raw buffer (manually managed)
|
||||||
uint8_t inline_data[GATTS_INLINE_DATA_SIZE]; // 40 bytes when stored inline
|
uint16_t data_len; // Track size separately
|
||||||
} data; // 40 bytes total
|
} gatts;
|
||||||
uint16_t data_len; // 2 bytes
|
} event_; // 80 bytes
|
||||||
esp_gatt_if_t gatts_if; // 1 byte
|
|
||||||
bool is_inline; // 1 byte - true when data is stored inline
|
|
||||||
} gatts; // Total: 80 bytes
|
|
||||||
} event_; // 80 bytes
|
|
||||||
|
|
||||||
ble_event_t type_;
|
ble_event_t type_;
|
||||||
|
|
||||||
@@ -263,29 +236,6 @@ class BLEEvent {
|
|||||||
const esp_ble_sec_t &security() const { return event_.gap.security; }
|
const esp_ble_sec_t &security() const { return event_.gap.security; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to copy data with inline storage optimization
|
|
||||||
template<typename EventStruct, size_t InlineSize>
|
|
||||||
void copy_data_with_inline_storage_(EventStruct &event, const uint8_t *src_data, uint16_t len,
|
|
||||||
uint8_t **param_value_ptr) {
|
|
||||||
event.data_len = len;
|
|
||||||
if (len > 0) {
|
|
||||||
if (len <= InlineSize) {
|
|
||||||
event.is_inline = true;
|
|
||||||
memcpy(event.data.inline_data, src_data, len);
|
|
||||||
*param_value_ptr = event.data.inline_data;
|
|
||||||
} else {
|
|
||||||
event.is_inline = false;
|
|
||||||
event.data.heap_data = new uint8_t[len];
|
|
||||||
memcpy(event.data.heap_data, src_data, len);
|
|
||||||
*param_value_ptr = event.data.heap_data;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event.is_inline = false;
|
|
||||||
event.data.heap_data = nullptr;
|
|
||||||
*param_value_ptr = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize GAP event data
|
// Initialize GAP event data
|
||||||
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
this->event_.gap.gap_event = e;
|
this->event_.gap.gap_event = e;
|
||||||
@@ -370,37 +320,48 @@ class BLEEvent {
|
|||||||
this->event_.gattc.gattc_if = i;
|
this->event_.gattc.gattc_if = i;
|
||||||
|
|
||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
// Zero out the param struct when null
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
memset(&this->event_.gattc.gattc_param, 0, sizeof(this->event_.gattc.gattc_param));
|
this->event_.gattc.data = nullptr;
|
||||||
this->event_.gattc.is_inline = false;
|
|
||||||
this->event_.gattc.data.heap_data = nullptr;
|
|
||||||
this->event_.gattc.data_len = 0;
|
this->event_.gattc.data_len = 0;
|
||||||
return; // Invalid event, but we can't log in header file
|
return; // Invalid event, but we can't log in header file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy param struct inline (no heap allocation!)
|
// Heap-allocate param and data
|
||||||
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
|
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||||
// along with small data payloads, eliminating all heap allocations for typical BLE operations
|
// while GAP events (99%) are stored inline to minimize memory usage
|
||||||
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
|
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||||
// is only valid during the callback and will be reused/freed after we return
|
// - The BLEEvent owns the allocated memory for its lifetime
|
||||||
this->event_.gattc.gattc_param = *p;
|
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||||
|
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||||
|
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||||
|
|
||||||
// Copy data for events that need it
|
// Copy data for events that need it
|
||||||
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
|
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
|
||||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case ESP_GATTC_NOTIFY_EVT:
|
case ESP_GATTC_NOTIFY_EVT:
|
||||||
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
|
this->event_.gattc.data_len = p->notify.value_len;
|
||||||
this->event_.gattc, p->notify.value, p->notify.value_len, &this->event_.gattc.gattc_param.notify.value);
|
if (p->notify.value_len > 0) {
|
||||||
|
this->event_.gattc.data = new uint8_t[p->notify.value_len];
|
||||||
|
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
||||||
|
} else {
|
||||||
|
this->event_.gattc.data = nullptr;
|
||||||
|
}
|
||||||
|
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data;
|
||||||
break;
|
break;
|
||||||
case ESP_GATTC_READ_CHAR_EVT:
|
case ESP_GATTC_READ_CHAR_EVT:
|
||||||
case ESP_GATTC_READ_DESCR_EVT:
|
case ESP_GATTC_READ_DESCR_EVT:
|
||||||
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
|
this->event_.gattc.data_len = p->read.value_len;
|
||||||
this->event_.gattc, p->read.value, p->read.value_len, &this->event_.gattc.gattc_param.read.value);
|
if (p->read.value_len > 0) {
|
||||||
|
this->event_.gattc.data = new uint8_t[p->read.value_len];
|
||||||
|
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
||||||
|
} else {
|
||||||
|
this->event_.gattc.data = nullptr;
|
||||||
|
}
|
||||||
|
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->event_.gattc.is_inline = false;
|
this->event_.gattc.data = nullptr;
|
||||||
this->event_.gattc.data.heap_data = nullptr;
|
|
||||||
this->event_.gattc.data_len = 0;
|
this->event_.gattc.data_len = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -412,32 +373,37 @@ class BLEEvent {
|
|||||||
this->event_.gatts.gatts_if = i;
|
this->event_.gatts.gatts_if = i;
|
||||||
|
|
||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
// Zero out the param struct when null
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
memset(&this->event_.gatts.gatts_param, 0, sizeof(this->event_.gatts.gatts_param));
|
this->event_.gatts.data = nullptr;
|
||||||
this->event_.gatts.is_inline = false;
|
|
||||||
this->event_.gatts.data.heap_data = nullptr;
|
|
||||||
this->event_.gatts.data_len = 0;
|
this->event_.gatts.data_len = 0;
|
||||||
return; // Invalid event, but we can't log in header file
|
return; // Invalid event, but we can't log in header file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy param struct inline (no heap allocation!)
|
// Heap-allocate param and data
|
||||||
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
|
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||||
// along with small data payloads, eliminating all heap allocations for typical BLE operations
|
// while GAP events (99%) are stored inline to minimize memory usage
|
||||||
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
|
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||||
// is only valid during the callback and will be reused/freed after we return
|
// - The BLEEvent owns the allocated memory for its lifetime
|
||||||
this->event_.gatts.gatts_param = *p;
|
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||||
|
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||||
|
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||||
|
|
||||||
// Copy data for events that need it
|
// Copy data for events that need it
|
||||||
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
|
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
|
||||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case ESP_GATTS_WRITE_EVT:
|
case ESP_GATTS_WRITE_EVT:
|
||||||
copy_data_with_inline_storage_<decltype(this->event_.gatts), GATTS_INLINE_DATA_SIZE>(
|
this->event_.gatts.data_len = p->write.len;
|
||||||
this->event_.gatts, p->write.value, p->write.len, &this->event_.gatts.gatts_param.write.value);
|
if (p->write.len > 0) {
|
||||||
|
this->event_.gatts.data = new uint8_t[p->write.len];
|
||||||
|
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
|
||||||
|
} else {
|
||||||
|
this->event_.gatts.data = nullptr;
|
||||||
|
}
|
||||||
|
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->event_.gatts.is_inline = false;
|
this->event_.gatts.data = nullptr;
|
||||||
this->event_.gatts.data.heap_data = nullptr;
|
|
||||||
this->event_.gatts.data_len = 0;
|
this->event_.gatts.data_len = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -448,15 +414,6 @@ class BLEEvent {
|
|||||||
// The gap member in the union should be 80 bytes (including the gap_event enum)
|
// The gap member in the union should be 80 bytes (including the gap_event enum)
|
||||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
|
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
|
||||||
|
|
||||||
// Verify GATTC and GATTS structs don't exceed GAP struct size
|
|
||||||
// This ensures the union size is determined by GAP (the most common event type)
|
|
||||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gattc)) <=
|
|
||||||
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
|
|
||||||
"gattc_event struct exceeds gap_event size - union size would increase");
|
|
||||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gatts)) <=
|
|
||||||
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
|
|
||||||
"gatts_event struct exceeds gap_event size - union size would increase");
|
|
||||||
|
|
||||||
// Verify esp_ble_sec_t fits within our union
|
// Verify esp_ble_sec_t fits within our union
|
||||||
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");
|
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include <esp_gap_ble_api.h>
|
#include <esp_gap_ble_api.h>
|
||||||
#include <esp_gatt_defs.h>
|
#include <esp_gatt_defs.h>
|
||||||
#include <esp_gattc_api.h>
|
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome::esp32_ble_client {
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
|||||||
return false;
|
return false;
|
||||||
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
||||||
return false;
|
return false;
|
||||||
if (this->state_ != espbt::ClientState::IDLE)
|
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->log_event_("Found device");
|
this->log_event_("Found device");
|
||||||
@@ -112,19 +111,43 @@ void BLEClientBase::connect() {
|
|||||||
this->remote_addr_type_);
|
this->remote_addr_type_);
|
||||||
this->paired_ = false;
|
this->paired_ = false;
|
||||||
|
|
||||||
// Determine connection parameters based on connection type
|
// Set preferred connection parameters before connecting
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
// Use FAST for all V3 connections (better latency and reliability)
|
||||||
// V3 without cache needs fast params for service discovery
|
// Use MEDIUM for V1/legacy connections (balanced performance)
|
||||||
this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
|
uint16_t min_interval, max_interval, timeout;
|
||||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
const char *param_type;
|
||||||
// V3 with cache can use medium params
|
|
||||||
this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
|
||||||
}
|
|
||||||
// For V1/Legacy, don't set params - use ESP-IDF defaults
|
|
||||||
|
|
||||||
// Open the connection
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||||
|
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
|
min_interval = FAST_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = FAST_MAX_CONN_INTERVAL;
|
||||||
|
timeout = FAST_CONN_TIMEOUT;
|
||||||
|
param_type = "fast";
|
||||||
|
} else {
|
||||||
|
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
|
timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
|
param_type = "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
||||||
|
0, // latency: 0
|
||||||
|
timeout);
|
||||||
|
if (param_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param_ret);
|
||||||
|
} else {
|
||||||
|
this->log_connection_params_(param_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now open the connection
|
||||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||||
this->handle_connection_result_(ret);
|
if (ret) {
|
||||||
|
this->log_gattc_warning_("esp_ble_gattc_open", ret);
|
||||||
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
|
} else {
|
||||||
|
this->set_state(espbt::ClientState::CONNECTING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
||||||
@@ -136,7 +159,7 @@ void BLEClientBase::disconnect() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
|
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
|
||||||
this->address_str_.c_str());
|
this->address_str_.c_str());
|
||||||
this->want_disconnect_ = true;
|
this->want_disconnect_ = true;
|
||||||
return;
|
return;
|
||||||
@@ -149,11 +172,13 @@ void BLEClientBase::unconditional_disconnect() {
|
|||||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
|
||||||
this->conn_id_);
|
this->conn_id_);
|
||||||
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||||
this->log_error_("Already disconnecting");
|
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->conn_id_ == UNSET_CONN_ID) {
|
if (this->conn_id_ == UNSET_CONN_ID) {
|
||||||
this->log_error_("conn id unset, cannot disconnect");
|
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||||
@@ -168,7 +193,8 @@ void BLEClientBase::unconditional_disconnect() {
|
|||||||
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ == espbt::ClientState::READY_TO_CONNECT || this->state_ == espbt::ClientState::DISCOVERED) {
|
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
||||||
|
this->state_ == espbt::ClientState::DISCOVERED) {
|
||||||
this->set_address(0);
|
this->set_address(0);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
} else {
|
} else {
|
||||||
@@ -208,51 +234,17 @@ void BLEClientBase::log_connection_params_(const char *param_type) {
|
|||||||
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::handle_connection_result_(esp_err_t ret) {
|
void BLEClientBase::restore_medium_conn_params_() {
|
||||||
if (ret) {
|
// Restore to medium connection parameters after initial connection phase
|
||||||
this->log_gattc_warning_("esp_ble_gattc_open", ret);
|
// This balances performance with bandwidth usage for normal operation
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
|
||||||
} else {
|
|
||||||
this->set_state(espbt::ClientState::CONNECTING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_error_(const char *message) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_error_(const char *message, int code) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_warning_(const char *message) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
|
|
||||||
uint16_t timeout, const char *param_type) {
|
|
||||||
esp_ble_conn_update_params_t conn_params = {{0}};
|
esp_ble_conn_update_params_t conn_params = {{0}};
|
||||||
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
||||||
conn_params.min_int = min_interval;
|
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
conn_params.max_int = max_interval;
|
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
conn_params.latency = latency;
|
conn_params.latency = 0;
|
||||||
conn_params.timeout = timeout;
|
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
this->log_connection_params_(param_type);
|
this->log_connection_params_("medium");
|
||||||
esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
|
esp_ble_gap_update_conn_params(&conn_params);
|
||||||
if (err != ESP_OK) {
|
|
||||||
this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
|
||||||
const char *param_type) {
|
|
||||||
// Set preferred connection parameters before connecting
|
|
||||||
// These will be used when establishing the connection
|
|
||||||
this->log_connection_params_(param_type);
|
|
||||||
esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||||
@@ -272,7 +264,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->app_id);
|
this->app_id);
|
||||||
this->gattc_if_ = esp_gattc_if;
|
this->gattc_if_ = esp_gattc_if;
|
||||||
} else {
|
} else {
|
||||||
this->log_error_("gattc app registration failed status", param->reg.status);
|
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
|
||||||
this->status_ = param->reg.status;
|
this->status_ = param->reg.status;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
}
|
}
|
||||||
@@ -284,21 +277,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->log_gattc_event_("OPEN");
|
this->log_gattc_event_("OPEN");
|
||||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||||
this->service_count_ = 0;
|
this->service_count_ = 0;
|
||||||
|
|
||||||
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
|
|
||||||
// error, if the error occurred at the BTA/GATT layer. This can result in the event
|
|
||||||
// arriving after we've already transitioned to IDLE state.
|
|
||||||
if (this->state_ == espbt::ClientState::IDLE) {
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), param->open.status);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->state_ != espbt::ClientState::CONNECTING) {
|
if (this->state_ != espbt::ClientState::CONNECTING) {
|
||||||
// This should not happen but lets log it in case it does
|
// This should not happen but lets log it in case it does
|
||||||
// because it means we have a bad assumption about how the
|
// because it means we have a bad assumption about how the
|
||||||
// ESP BT stack works.
|
// ESP BT stack works.
|
||||||
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
|
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_,
|
||||||
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
|
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
|
||||||
}
|
}
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
@@ -318,14 +301,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->set_state(espbt::ClientState::CONNECTED);
|
this->set_state(espbt::ClientState::CONNECTED);
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
// Cached connections already connected with medium parameters, no update needed
|
// Restore to medium connection parameters for cached connections too
|
||||||
|
this->restore_medium_conn_params_();
|
||||||
// only set our state, subclients might have more stuff to do yet.
|
// only set our state, subclients might have more stuff to do yet.
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// For V3_WITHOUT_CACHE, we already set fast params before connecting
|
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
|
||||||
// No need to update them again here
|
|
||||||
this->log_event_("Searching for services");
|
|
||||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -350,7 +332,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
// Check if we were disconnected while waiting for service discovery
|
// Check if we were disconnected while waiting for service discovery
|
||||||
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
||||||
this->state_ == espbt::ClientState::CONNECTED) {
|
this->state_ == espbt::ClientState::CONNECTED) {
|
||||||
this->log_warning_("Remote closed during discovery");
|
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
|
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
|
||||||
this->address_str_.c_str(), param->disconnect.reason);
|
this->address_str_.c_str(), param->disconnect.reason);
|
||||||
@@ -387,8 +370,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
if (this->conn_id_ != param->search_res.conn_id)
|
if (this->conn_id_ != param->search_res.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->service_count_++;
|
this->service_count_++;
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
|
||||||
// V3 clients don't need services initialized since
|
// V3 clients don't need services initialized since
|
||||||
// as they use the ESP APIs to get services.
|
// as they use the ESP APIs to get services.
|
||||||
break;
|
break;
|
||||||
@@ -407,11 +389,12 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("SEARCH_CMPL");
|
this->log_gattc_event_("SEARCH_CMPL");
|
||||||
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
// For V3 connections, restore to medium connection parameters after service discovery
|
||||||
// This balances performance with bandwidth usage after the critical discovery phase
|
// This balances performance with bandwidth usage after the critical discovery phase
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||||
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
} else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
|
this->restore_medium_conn_params_();
|
||||||
|
} else {
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
#ifdef USE_ESP32_BLE_DEVICE
|
||||||
for (auto &svc : this->services_) {
|
for (auto &svc : this->services_) {
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
@@ -495,11 +478,6 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
|
||||||
this->log_gattc_event_("UNREG_FOR_NOTIFY");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// ideally would check all other events for matching conn_id
|
// ideally would check all other events for matching conn_id
|
||||||
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
|
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
|
||||||
@@ -528,14 +506,16 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
|
|||||||
return;
|
return;
|
||||||
esp_bd_addr_t bd_addr;
|
esp_bd_addr_t bd_addr;
|
||||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||||
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
format_hex(bd_addr, 6).c_str());
|
format_hex(bd_addr, 6).c_str());
|
||||||
if (!param->ble_security.auth_cmpl.success) {
|
if (!param->ble_security.auth_cmpl.success) {
|
||||||
this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
|
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->ble_security.auth_cmpl.fail_reason);
|
||||||
} else {
|
} else {
|
||||||
this->paired_ = true;
|
this->paired_ = true;
|
||||||
ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||||
param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
|
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||||
|
param->ble_security.auth_cmpl.auth_mode);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@@ -133,18 +133,10 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
|
|
||||||
void log_event_(const char *name);
|
void log_event_(const char *name);
|
||||||
void log_gattc_event_(const char *name);
|
void log_gattc_event_(const char *name);
|
||||||
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
void restore_medium_conn_params_();
|
||||||
const char *param_type);
|
|
||||||
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
|
||||||
const char *param_type);
|
|
||||||
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
|
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
|
||||||
void log_gattc_warning_(const char *operation, esp_err_t err);
|
void log_gattc_warning_(const char *operation, esp_err_t err);
|
||||||
void log_connection_params_(const char *param_type);
|
void log_connection_params_(const char *param_type);
|
||||||
void handle_connection_result_(esp_err_t ret);
|
|
||||||
// Compact error logging helpers to reduce flash usage
|
|
||||||
void log_error_(const char *message);
|
|
||||||
void log_error_(const char *message, int code);
|
|
||||||
void log_warning_(const char *message);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esphome::esp32_ble_client
|
||||||
|
@@ -30,7 +30,7 @@ from esphome.const import (
|
|||||||
CONF_SERVICE_UUID,
|
CONF_SERVICE_UUID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ async def to_code(config):
|
|||||||
# This needs to be run as a job with very low priority so that all components have
|
# This needs to be run as a job with very low priority so that all components have
|
||||||
# chance to call register_ble_tracker and register_client before the list is checked
|
# chance to call register_ble_tracker and register_client before the list is checked
|
||||||
# and added to the global defines list.
|
# and added to the global defines list.
|
||||||
@coroutine_with_priority(CoroPriority.FINAL)
|
@coroutine_with_priority(-1000)
|
||||||
async def _add_ble_features():
|
async def _add_ble_features():
|
||||||
# Add feature-specific defines based on what's needed
|
# Add feature-specific defines based on what's needed
|
||||||
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
||||||
|
@@ -80,17 +80,14 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
|
|||||||
ESPBTUUID uuid_;
|
ESPBTUUID uuid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
|
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
|
||||||
public:
|
public:
|
||||||
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
bool parse_device(const ESPBTDevice &device) override { return false; }
|
bool parse_device(const ESPBTDevice &device) override { return false; }
|
||||||
#endif
|
|
||||||
void on_scan_end() override { this->trigger(); }
|
void on_scan_end() override { this->trigger(); }
|
||||||
};
|
};
|
||||||
|
#endif // USE_ESP32_BLE_DEVICE
|
||||||
|
|
||||||
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
|
@@ -49,6 +49,8 @@ const char *client_state_to_string(ClientState state) {
|
|||||||
return "DISCONNECTING";
|
return "DISCONNECTING";
|
||||||
case ClientState::IDLE:
|
case ClientState::IDLE:
|
||||||
return "IDLE";
|
return "IDLE";
|
||||||
|
case ClientState::SEARCHING:
|
||||||
|
return "SEARCHING";
|
||||||
case ClientState::DISCOVERED:
|
case ClientState::DISCOVERED:
|
||||||
return "DISCOVERED";
|
return "DISCOVERED";
|
||||||
case ClientState::READY_TO_CONNECT:
|
case ClientState::READY_TO_CONNECT:
|
||||||
@@ -134,8 +136,9 @@ void ESP32BLETracker::loop() {
|
|||||||
ClientStateCounts counts = this->count_client_states_();
|
ClientStateCounts counts = this->count_client_states_();
|
||||||
if (counts != this->client_state_counts_) {
|
if (counts != this->client_state_counts_) {
|
||||||
this->client_state_counts_ = counts;
|
this->client_state_counts_ = counts;
|
||||||
ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
|
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
|
||||||
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
|
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
|
||||||
|
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->scanner_state_ == ScannerState::FAILED ||
|
if (this->scanner_state_ == ScannerState::FAILED ||
|
||||||
@@ -155,8 +158,10 @@ void ESP32BLETracker::loop() {
|
|||||||
https://github.com/espressif/esp-idf/issues/6688
|
https://github.com/espressif/esp-idf/issues/6688
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting;
|
||||||
|
|
||||||
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
|
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting &&
|
||||||
|
!promote_to_connecting) {
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||||
this->update_coex_preference_(false);
|
this->update_coex_preference_(false);
|
||||||
#endif
|
#endif
|
||||||
@@ -165,11 +170,12 @@ void ESP32BLETracker::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there is a discovered client and no connecting
|
// If there is a discovered client and no connecting
|
||||||
// clients, then promote the discovered client to ready to connect.
|
// clients and no clients using the scanner to search for
|
||||||
|
// devices, then promote the discovered client to ready to connect.
|
||||||
// We check both RUNNING and IDLE states because:
|
// We check both RUNNING and IDLE states because:
|
||||||
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
|
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
|
||||||
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
|
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
|
||||||
if (counts.discovered && !counts.connecting &&
|
if (promote_to_connecting &&
|
||||||
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
|
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
|
||||||
this->try_promote_discovered_clients_();
|
this->try_promote_discovered_clients_();
|
||||||
}
|
}
|
||||||
@@ -301,7 +307,14 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
|||||||
|
|
||||||
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||||
// Process the scan result immediately
|
// Process the scan result immediately
|
||||||
this->process_scan_result_(scan_result);
|
bool found_discovered_client = this->process_scan_result_(scan_result);
|
||||||
|
|
||||||
|
// If we found a discovered client that needs promotion, stop scanning
|
||||||
|
// This replaces the promote_to_connecting logic from loop()
|
||||||
|
if (found_discovered_client && this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
ESP_LOGD(TAG, "Found discovered client, stopping scan for connection");
|
||||||
|
this->stop_scan_();
|
||||||
|
}
|
||||||
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
||||||
// Scan finished on its own
|
// Scan finished on its own
|
||||||
if (this->scanner_state_ != ScannerState::RUNNING) {
|
if (this->scanner_state_ != ScannerState::RUNNING) {
|
||||||
@@ -627,8 +640,9 @@ void ESP32BLETracker::dump_config() {
|
|||||||
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
|
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
|
||||||
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
|
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
|
||||||
ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
|
ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
|
||||||
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
|
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
|
||||||
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
|
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
|
||||||
|
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
|
||||||
if (this->scan_start_fail_count_) {
|
if (this->scan_start_fail_count_) {
|
||||||
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
|
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
|
||||||
}
|
}
|
||||||
@@ -706,9 +720,20 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
|
|||||||
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
|
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ESP32BLETracker::has_connecting_clients_() const {
|
||||||
|
for (auto *client : this->clients_) {
|
||||||
|
auto state = client->state();
|
||||||
|
if (state == ClientState::CONNECTING || state == ClientState::READY_TO_CONNECT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
#endif // USE_ESP32_BLE_DEVICE
|
||||||
|
|
||||||
void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
||||||
|
bool found_discovered_client = false;
|
||||||
|
|
||||||
// Process raw advertisements
|
// Process raw advertisements
|
||||||
if (this->raw_advertisements_) {
|
if (this->raw_advertisements_) {
|
||||||
for (auto *listener : this->listeners_) {
|
for (auto *listener : this->listeners_) {
|
||||||
@@ -734,6 +759,14 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
|||||||
for (auto *client : this->clients_) {
|
for (auto *client : this->clients_) {
|
||||||
if (client->parse_device(device)) {
|
if (client->parse_device(device)) {
|
||||||
found = true;
|
found = true;
|
||||||
|
// Check if this client is discovered and needs promotion
|
||||||
|
if (client->state() == ClientState::DISCOVERED) {
|
||||||
|
// Only check for connecting clients if we found a discovered client
|
||||||
|
// This matches the original logic: !connecting && client->state() == DISCOVERED
|
||||||
|
if (!this->has_connecting_clients_()) {
|
||||||
|
found_discovered_client = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +775,8 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
|||||||
}
|
}
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
#endif // USE_ESP32_BLE_DEVICE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return found_discovered_client;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
|
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
|
||||||
|
@@ -141,10 +141,12 @@ class ESPBTDeviceListener {
|
|||||||
struct ClientStateCounts {
|
struct ClientStateCounts {
|
||||||
uint8_t connecting = 0;
|
uint8_t connecting = 0;
|
||||||
uint8_t discovered = 0;
|
uint8_t discovered = 0;
|
||||||
|
uint8_t searching = 0;
|
||||||
uint8_t disconnecting = 0;
|
uint8_t disconnecting = 0;
|
||||||
|
|
||||||
bool operator==(const ClientStateCounts &other) const {
|
bool operator==(const ClientStateCounts &other) const {
|
||||||
return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting;
|
return connecting == other.connecting && discovered == other.discovered && searching == other.searching &&
|
||||||
|
disconnecting == other.disconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
|
bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
|
||||||
@@ -157,6 +159,8 @@ enum class ClientState : uint8_t {
|
|||||||
DISCONNECTING,
|
DISCONNECTING,
|
||||||
// Connection is idle, no device detected.
|
// Connection is idle, no device detected.
|
||||||
IDLE,
|
IDLE,
|
||||||
|
// Searching for device.
|
||||||
|
SEARCHING,
|
||||||
// Device advertisement found.
|
// Device advertisement found.
|
||||||
DISCOVERED,
|
DISCOVERED,
|
||||||
// Device is discovered and the scanner is stopped
|
// Device is discovered and the scanner is stopped
|
||||||
@@ -288,7 +292,12 @@ class ESP32BLETracker : public Component,
|
|||||||
/// Common cleanup logic when transitioning scanner to IDLE state
|
/// Common cleanup logic when transitioning scanner to IDLE state
|
||||||
void cleanup_scan_state_(bool is_stop_complete);
|
void cleanup_scan_state_(bool is_stop_complete);
|
||||||
/// Process a single scan result immediately
|
/// Process a single scan result immediately
|
||||||
void process_scan_result_(const BLEScanResult &scan_result);
|
/// Returns true if a discovered client needs promotion to READY_TO_CONNECT
|
||||||
|
bool process_scan_result_(const BLEScanResult &scan_result);
|
||||||
|
#ifdef USE_ESP32_BLE_DEVICE
|
||||||
|
/// Check if any clients are in connecting or ready to connect state
|
||||||
|
bool has_connecting_clients_() const;
|
||||||
|
#endif
|
||||||
/// Handle scanner failure states
|
/// Handle scanner failure states
|
||||||
void handle_scanner_failure_();
|
void handle_scanner_failure_();
|
||||||
/// Try to promote discovered clients to ready to connect
|
/// Try to promote discovered clients to ready to connect
|
||||||
@@ -312,6 +321,9 @@ class ESP32BLETracker : public Component,
|
|||||||
case ClientState::DISCOVERED:
|
case ClientState::DISCOVERED:
|
||||||
counts.discovered++;
|
counts.discovered++;
|
||||||
break;
|
break;
|
||||||
|
case ClientState::SEARCHING:
|
||||||
|
counts.searching++;
|
||||||
|
break;
|
||||||
case ClientState::CONNECTING:
|
case ClientState::CONNECTING:
|
||||||
case ClientState::READY_TO_CONNECT:
|
case ClientState::READY_TO_CONNECT:
|
||||||
counts.connecting++;
|
counts.connecting++;
|
||||||
|
@@ -17,7 +17,7 @@ from esphome.const import (
|
|||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
ThreadModel,
|
ThreadModel,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.helpers import copy_file_if_changed
|
from esphome.helpers import copy_file_if_changed
|
||||||
|
|
||||||
from .boards import BOARDS, ESP8266_LD_SCRIPTS
|
from .boards import BOARDS, ESP8266_LD_SCRIPTS
|
||||||
@@ -176,7 +176,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.PLATFORM)
|
@coroutine_with_priority(1000)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add(esp8266_ns.setup_preferences())
|
cg.add(esp8266_ns.setup_preferences())
|
||||||
|
|
||||||
|
@@ -58,8 +58,8 @@ extern "C" void resetPins() { // NOLINT
|
|||||||
|
|
||||||
#ifdef USE_ESP8266_EARLY_PIN_INIT
|
#ifdef USE_ESP8266_EARLY_PIN_INIT
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
uint8_t mode = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]);
|
uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
|
||||||
uint8_t level = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]);
|
uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
|
||||||
if (mode != 255)
|
if (mode != 255)
|
||||||
pinMode(i, mode); // NOLINT
|
pinMode(i, mode); // NOLINT
|
||||||
if (level != 255)
|
if (level != 255)
|
||||||
|
@@ -17,7 +17,7 @@ from esphome.const import (
|
|||||||
CONF_PULLUP,
|
CONF_PULLUP,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
from . import boards
|
from . import boards
|
||||||
from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns
|
from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns
|
||||||
@@ -188,7 +188,7 @@ async def esp8266_pin_to_code(config):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
|
@coroutine_with_priority(-999.0)
|
||||||
async def add_pin_initial_states_array():
|
async def add_pin_initial_states_array():
|
||||||
# Add includes at the very end, so that they override everything
|
# Add includes at the very end, so that they override everything
|
||||||
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
|
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
|
||||||
@@ -199,11 +199,11 @@ async def add_pin_initial_states_array():
|
|||||||
|
|
||||||
cg.add_global(
|
cg.add_global(
|
||||||
cg.RawExpression(
|
cg.RawExpression(
|
||||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
|
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
cg.add_global(
|
cg.add_global(
|
||||||
cg.RawExpression(
|
cg.RawExpression(
|
||||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
|
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
|
|
||||||
#include <c_types.h>
|
#include <c_types.h>
|
||||||
#include <cinttypes>
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "spi_flash.h"
|
#include "spi_flash.h"
|
||||||
}
|
}
|
||||||
@@ -13,7 +12,7 @@ extern "C" {
|
|||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp8266 {
|
namespace esp8266 {
|
||||||
@@ -68,8 +67,6 @@ static uint32_t get_esp8266_flash_sector() {
|
|||||||
}
|
}
|
||||||
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
|
||||||
|
|
||||||
static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
|
|
||||||
|
|
||||||
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
|
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
|
||||||
uint32_t crc = type;
|
uint32_t crc = type;
|
||||||
while (first != last) {
|
while (first != last) {
|
||||||
@@ -120,42 +117,47 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
|
|||||||
|
|
||||||
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
||||||
public:
|
public:
|
||||||
|
size_t offset = 0;
|
||||||
uint32_t type = 0;
|
uint32_t type = 0;
|
||||||
uint16_t offset = 0;
|
|
||||||
uint8_t length_words = 0; // Max 255 words (1020 bytes of data)
|
|
||||||
bool in_flash = false;
|
bool in_flash = false;
|
||||||
|
size_t length_words = 0;
|
||||||
|
|
||||||
bool save(const uint8_t *data, size_t len) override {
|
bool save(const uint8_t *data, size_t len) override {
|
||||||
if (bytes_to_words(len) != length_words) {
|
if ((len + 3) / 4 != length_words) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
std::vector<uint32_t> buffer;
|
||||||
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
|
buffer.resize(length_words + 1);
|
||||||
memcpy(buffer.get(), data, len);
|
memcpy(buffer.data(), data, len);
|
||||||
buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
|
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
||||||
|
|
||||||
if (in_flash) {
|
if (in_flash) {
|
||||||
return save_to_flash(offset, buffer.get(), buffer_size);
|
return save_to_flash(offset, buffer.data(), buffer.size());
|
||||||
|
} else {
|
||||||
|
return save_to_rtc(offset, buffer.data(), buffer.size());
|
||||||
}
|
}
|
||||||
return save_to_rtc(offset, buffer.get(), buffer_size);
|
|
||||||
}
|
}
|
||||||
bool load(uint8_t *data, size_t len) override {
|
bool load(uint8_t *data, size_t len) override {
|
||||||
if (bytes_to_words(len) != length_words) {
|
if ((len + 3) / 4 != length_words) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
size_t buffer_size = static_cast<size_t>(length_words) + 1;
|
std::vector<uint32_t> buffer;
|
||||||
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
|
buffer.resize(length_words + 1);
|
||||||
bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
|
bool ret;
|
||||||
: load_from_rtc(offset, buffer.get(), buffer_size);
|
if (in_flash) {
|
||||||
|
ret = load_from_flash(offset, buffer.data(), buffer.size());
|
||||||
|
} else {
|
||||||
|
ret = load_from_rtc(offset, buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type);
|
uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
||||||
if (buffer[length_words] != crc) {
|
if (buffer[buffer.size() - 1] != crc) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(data, buffer.get(), len);
|
memcpy(data, buffer.data(), len);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -176,20 +178,16 @@ class ESP8266Preferences : public ESPPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||||
uint32_t length_words = bytes_to_words(length);
|
uint32_t length_words = (length + 3) / 4;
|
||||||
if (length_words > 255) {
|
|
||||||
ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (in_flash) {
|
if (in_flash) {
|
||||||
uint32_t start = current_flash_offset;
|
uint32_t start = current_flash_offset;
|
||||||
uint32_t end = start + length_words + 1;
|
uint32_t end = start + length_words + 1;
|
||||||
if (end > ESP8266_FLASH_STORAGE_SIZE)
|
if (end > ESP8266_FLASH_STORAGE_SIZE)
|
||||||
return {};
|
return {};
|
||||||
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
pref->offset = static_cast<uint16_t>(start);
|
pref->offset = start;
|
||||||
pref->type = type;
|
pref->type = type;
|
||||||
pref->length_words = static_cast<uint8_t>(length_words);
|
pref->length_words = length_words;
|
||||||
pref->in_flash = true;
|
pref->in_flash = true;
|
||||||
current_flash_offset = end;
|
current_flash_offset = end;
|
||||||
return {pref};
|
return {pref};
|
||||||
@@ -215,9 +213,9 @@ class ESP8266Preferences : public ESPPreferences {
|
|||||||
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
|
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
|
||||||
|
|
||||||
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
pref->offset = static_cast<uint16_t>(rtc_offset);
|
pref->offset = rtc_offset;
|
||||||
pref->type = type;
|
pref->type = type;
|
||||||
pref->length_words = static_cast<uint8_t>(length_words);
|
pref->length_words = length_words;
|
||||||
pref->in_flash = false;
|
pref->in_flash = false;
|
||||||
current_offset += length_words + 1;
|
current_offset += length_words + 1;
|
||||||
return pref;
|
return pref;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user