1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 00:21:56 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Jesse Hills
bc3212d981 [quality] Use `uint8_t instead of unsigned char` 2025-08-13 08:53:23 +12:00
368 changed files with 2544 additions and 6153 deletions

View File

@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
## 2. Core Technologies & Stack
* **Languages:** Python (>=3.11), C++ (gnu++20)
* **Languages:** Python (>=3.10), C++ (gnu++20)
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
* **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.
* **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.
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.
@@ -60,7 +60,7 @@ This document provides essential context for AI models interacting with this pro
├── __init__.py # Component configuration schema and code generation
├── [component].h # C++ header file (if needed)
├── [component].cpp # C++ implementation (if needed)
└── [platform]/ # Platform-specific implementations
└── [platform]/ # Platform-specific implementations
├── __init__.py # Platform-specific configuration
├── [platform].h # Platform C++ header
└── [platform].cpp # Platform C++ implementation
@@ -150,8 +150,7 @@ This document provides essential context for AI models interacting with this pro
* **Configuration Validation:**
* **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)`.
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
* **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
* **Schema Extensions:**
```python
CONFIG_SCHEMA = cv.Schema({ ... })

View File

@@ -1 +1 @@
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273

View File

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

View File

@@ -32,7 +32,7 @@ jobs:
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
- name: Auto Label PR
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
@@ -105,9 +105,7 @@ jobs:
// Calculate data from PR files
const changedFiles = prFiles.map(file => file.filename);
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
const totalChanges = totalAdditions + totalDeletions;
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
console.log('Current labels:', currentLabels.join(', '));
console.log('Changed files:', changedFiles.length);
@@ -233,21 +231,16 @@ jobs:
// Strategy: PR size detection
async function detectPRSize() {
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) {
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
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
labels.add('too-big');
@@ -382,7 +375,7 @@ jobs:
const labels = new Set();
// 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');
}
@@ -419,13 +412,10 @@ jobs:
// Too big message
if (finalLabels.includes('too-big')) {
const testAdditions = prFiles
const testChanges = 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);
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
const nonTestChanges = totalChanges - testChanges;
const tooManyLabels = finalLabels.length > MAX_LABELS;
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -47,7 +47,7 @@ jobs:
fi
- if: failure()
name: Review PR
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
await github.rest.pulls.createReview({
@@ -70,7 +70,7 @@ jobs:
esphome/components/api/api_pb2_service.*
- if: success()
name: Dismiss review
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -41,7 +41,7 @@ jobs:
- if: failure()
name: Request changes
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
await github.rest.pulls.createReview({
@@ -54,7 +54,7 @@ jobs:
- if: success()
name: Dismiss review
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
- name: Set up Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -156,7 +156,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.1
uses: codecov/codecov-action@v5.4.3
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
@@ -217,7 +217,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.13"
- name: Restore Python virtual environment

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request reviews from component codeowners
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
const owner = context.repo.owner;

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Add external component comment
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify codeowners for component issues
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
const owner = context.repo.owner;

24
.github/workflows/needs-docs.yml vendored Normal file
View 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');
}

View File

@@ -62,7 +62,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.x"
- name: Build
@@ -70,7 +70,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.13.0
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
skip-existing: true
@@ -94,7 +94,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -220,7 +220,7 @@ jobs:
- deploy-manifest
steps:
- name: Trigger Workflow
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
script: |
@@ -246,7 +246,7 @@ jobs:
environment: ${{ needs.init.outputs.deploy_env }}
steps:
- name: Trigger Workflow
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: |

View File

@@ -17,7 +17,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.0.0
- uses: actions/stale@v9.1.0
with:
days-before-pr-stale: 90
days-before-pr-close: 7
@@ -37,7 +37,7 @@ jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.0.0
- uses: actions/stale@v9.1.0
with:
days-before-pr-stale: -1
days-before-pr-close: -1

View File

@@ -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@v8.0.0
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 }}');
}

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: 3.13

View File

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

View File

@@ -89,7 +89,6 @@ esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.9.0-dev
PROJECT_NUMBER = 2025.8.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -132,17 +132,14 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
if CORE.address and (
(show_ota and "ota" in CORE.config)
or (show_api and "api" in CORE.config)
if (show_ota and "ota" in CORE.config) or (
show_api and "api" in CORE.config
):
resolved.append(CORE.address)
elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT")
else:
resolved.append(device)
if not resolved:
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
return resolved
# 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
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
return mqtt.show_logs(

View File

@@ -61,10 +61,11 @@ void AbsoluteHumidityComponent::loop() {
ESP_LOGW(TAG, "No valid state from temperature sensor!");
}
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->status_set_warning("Unable to calculate absolute humidity.");
this->status_set_warning();
return;
}
@@ -86,8 +87,9 @@ void AbsoluteHumidityComponent::loop() {
es = es_wobus(temperature_c);
break;
default:
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
this->publish_state(NAN);
this->status_set_error("Invalid saturation vapor pressure equation selection!");
this->status_set_error();
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
uint8_t rev_newaddress = ~newaddress;
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)) {
this->error_code_ = COMMUNICATION_FAILED;
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) {
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)) {
this->error_code_ = COMMUNICATION_FAILED;
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 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;
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
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;
}
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 esphome

View File

@@ -1,9 +1,9 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ags10 {
@@ -99,6 +99,16 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
* 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);
/**
* 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> {

View File

@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.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])
await esp32_ble_tracker.register_ble_device(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
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.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)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)

View File

@@ -29,6 +29,22 @@ namespace 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) {
// code based on demo code sent by www.aosong.com
// no further documentation.
@@ -70,7 +86,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
humidity = raw * 9.5367431640625e-5;
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
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() {

View File

@@ -21,9 +21,9 @@
// SOFTWARE.
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
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; }
protected:
uint8_t crc8_(uint8_t *data, uint8_t len);
bool convert_(uint8_t *data, float &humidity, float &temperature);
bool reset_register_(uint8_t reg);

View File

@@ -24,7 +24,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"]
@@ -134,7 +134,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.WEB)
@coroutine_with_priority(40.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -321,7 +321,6 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
)
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])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
cg.add(var.set_service("esphome.tag_scanned"))

View File

@@ -1712,7 +1712,6 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
}
message BluetoothScannerSetModeRequest {

View File

@@ -289,26 +289,16 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
return 0; // Doesn't fit
}
// Allocate buffer space - pass payload size, allocation functions add header/footer space
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
: conn->allocate_batch_message_buffer(calculated_size);
// Get buffer size after allocation (which includes header padding)
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
if (is_single || conn->flags_.batch_first_message) {
// Single message or first batch message
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
if (conn->flags_.batch_first_message) {
conn->flags_.batch_first_message = false;
}
} else {
// Batch message second or later
// Add padding for previous message footer + this message header
size_t current_size = shared_buf.size();
shared_buf.reserve(current_size + total_calculated_size);
shared_buf.resize(current_size + footer_size + header_padding);
}
size_t size_before_encode = shared_buf.size();
// Encode directly into buffer
size_t size_before_encode = shared_buf.size();
msg.encode({&shared_buf});
msg.encode(buffer);
// Calculate actual encoded size (not including header that was already added)
size_t actual_payload_size = shared_buf.size() - size_before_encode;
@@ -465,7 +455,9 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
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);
}
@@ -1423,7 +1415,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
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
#if defined(USE_ESP8266) || defined(USE_ESP32)
@@ -1626,6 +1620,14 @@ bool APIConnection::schedule_batch_() {
return true;
}
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
this->flags_.batch_first_message = false;
return result;
}
void APIConnection::process_batch_() {
// Ensure PacketInfo remains trivially destructible for our placement new approach
static_assert(std::is_trivially_destructible<PacketInfo>::value,
@@ -1733,7 +1735,7 @@ void APIConnection::process_batch_() {
}
remaining_size -= payload_size;
// Calculate where the next message's header padding will start
// Current buffer size + footer space for this message
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
current_offset = shared_buf.size() + footer_size;
}

View File

@@ -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
#endif
class APIConnection final : public APIServerConnection {
class APIConnection : public APIServerConnection {
public:
friend class APIServer;
friend class ListEntitiesIterator;
@@ -252,21 +252,44 @@ class APIConnection final : public APIServerConnection {
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, header_padding,
reserve_size + header_padding + this->helper_->frame_footer_size());
return {&shared_buf};
}
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
shared_buf.clear();
// Reserve space for header padding + message + footer
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
shared_buf.reserve(total_size);
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
// Resize to add header padding so message encoding starts at the correct position
shared_buf.resize(header_padding);
return {&shared_buf};
}
// Prepare buffer for next message in batch
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
// Get reference to shared buffer (it maintains state between batch messages)
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
if (is_first_message) {
shared_buf.clear();
}
size_t current_size = shared_buf.size();
// Calculate padding to add:
// - First message: just header padding
// - Subsequent messages: footer for previous message + header padding for this message
size_t padding_to_add = is_first_message
? this->helper_->frame_header_padding()
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
// Reserve space for padding + message
shared_buf.reserve(current_size + padding_to_add + message_size);
// Resize to add the padding bytes
shared_buf.resize(current_size + padding_to_add);
return {&shared_buf};
}
bool try_to_clear_buffer(bool log_out_of_space);
@@ -274,6 +297,10 @@ class APIConnection final : public APIServerConnection {
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
// Buffer allocator methods for batch processing
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
protected:
// Helper function to handle authentication completion
void complete_authentication_();
@@ -301,17 +328,9 @@ class APIConnection final : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
// Try to use static reference first to avoid allocation
StringRef static_ref = entity->get_object_id_ref_for_api_();
// Store dynamic string outside the if-else to maintain lifetime
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));
}
// IMPORTANT: get_object_id() may return a temporary std::string
std::string object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id));
if (entity->has_own_name()) {
msg.set_name(entity->get_name());

View File

@@ -104,9 +104,9 @@ class APIFrameHelper {
// The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
// 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
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
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }

View File

@@ -7,7 +7,7 @@
namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
class APINoiseFrameHelper : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
const ClientInfo *client_info)
@@ -25,6 +25,10 @@ class APINoiseFrameHelper final : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) 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:
APIError state_action_();

View File

@@ -5,7 +5,7 @@
namespace esphome::api {
class APIPlaintextFrameHelper final : public APIFrameHelper {
class APIPlaintextFrameHelper : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) {
@@ -22,6 +22,9 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) 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:
APIError try_read_frame_(std::vector<uint8_t> *frame);

View File

@@ -2153,12 +2153,10 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
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 {
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->configured_mode));
}
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {

File diff suppressed because it is too large Load Diff

View File

@@ -1135,7 +1135,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_);
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) {
dump_field(out, "int_array", it, 4);
@@ -1704,7 +1704,6 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
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 {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");

View File

@@ -8,70 +8,74 @@ namespace esphome::api {
static const char *const TAG = "api.proto";
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
const uint8_t *ptr = buffer;
const uint8_t *end = buffer + length;
while (ptr < end) {
uint32_t i = 0;
bool error = false;
while (i < length) {
uint32_t consumed;
// Parse field header
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
return;
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
break;
}
uint32_t tag = res->as_uint32();
uint32_t field_type = tag & 0b111;
uint32_t field_id = tag >> 3;
ptr += consumed;
uint32_t field_type = (res->as_uint32()) & 0b111;
uint32_t field_id = (res->as_uint32()) >> 3;
i += consumed;
switch (field_type) {
case 0: { // VarInt
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
return;
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
error = true;
break;
}
if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
}
ptr += consumed;
i += consumed;
break;
}
case 2: { // Length-delimited
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
return;
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
error = true;
break;
}
uint32_t field_length = res->as_uint32();
ptr += consumed;
if (ptr + field_length > end) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
return;
i += consumed;
if (field_length > length - i) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
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);
}
ptr += field_length;
i += field_length;
break;
}
case 5: { // 32-bit
if (ptr + 4 > end) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
if (length - i < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
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))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
ptr += 4;
i += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
return;
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
error = true;
break;
}
if (error) {
break;
}
}
}

View File

@@ -8,7 +8,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
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):
if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP

View File

@@ -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)
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
// Default is 143FH (20ms, 63ms)
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 ATM90E32Component::read16_transaction_(uint16_t a_register) {
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF);
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
this->transfer_array(data, 4);
uint16_t output = encode_uint16(data[2], data[3]);
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
this->disable();
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) {
const uint16_t val_h = this->read16_(addr_h);
const uint16_t val_l = this->read16_(addr_l);
this->enable();
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;
ESP_LOGVV(TAG,

View File

@@ -140,6 +140,7 @@ class ATM90E32Component : public PollingComponent,
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
#endif
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);
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
float get_local_phase_voltage_(uint8_t phase);

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
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"]
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
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using)

View File

@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
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"]
IS_PLATFORM_COMPONENT = True
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using)

View File

@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
i2c::ErrorCode err;
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);
err = this->read(data, sizeof(data));
ERROR_CHECK(err);

View File

@@ -59,7 +59,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION,
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.cpp_generator import MockObjClass
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)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(binary_sensor_ns.using)

View File

@@ -7,19 +7,6 @@ namespace 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) {
if (this->filter_list_ == nullptr) {
this->send_state_internal(new_state);

View File

@@ -10,10 +10,13 @@ namespace esphome {
namespace binary_sensor {
class BinarySensor;
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
#define LOG_BINARY_SENSOR(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
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) \
protected: \

View File

@@ -286,7 +286,6 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
async def to_code(config):
# Register the loggers this component needs
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
cg.add_define("USE_ESP32_BLE_UUID")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
@@ -63,6 +63,6 @@ async def to_code(config):
)
cg.add(var.set_char_uuid128(uuid128))
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
await output.register_output(var, config)
await ble_client.register_ble_node(var, config)
await cg.register_component(var, config)
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
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.only_with_esp_idf, cv.boolean
),

View File

@@ -12,30 +12,16 @@ namespace esphome::bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection";
// This function is allocation-free and directly packs UUIDs into the output array
// using precalculated constants for the Bluetooth base UUID
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
// out[0] = bytes 8-15 (big-endian)
// - For 128-bit UUIDs: use bytes 8-15 as-is
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
out[0] = uuid_source.len == ESP_UUID_LEN_128
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
<< 32) |
0x00001000ULL); // Base UUID bytes 8-11
// out[1] = bytes 0-7 (big-endian)
// - For 128-bit UUIDs: use bytes 0-7 as-is
// - For 16/32-bit UUIDs: use precalculated base UUID constant
out[1] = uuid_source.len == ESP_UUID_LEN_128
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
}
// Helper to fill UUID in the appropriate format based on client support and UUID type
@@ -133,7 +119,7 @@ void BluetoothConnection::loop() {
// Check if we should disable the loop
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
// - For other connections: Disable only after service discovery is complete
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->send_service_ == DONE_SENDING_SERVICES)) {
@@ -160,7 +146,10 @@ void BluetoothConnection::send_service_for_discovery_() {
if (this->send_service_ >= this->service_count_) {
this->send_service_ = DONE_SENDING_SERVICES;
this->proxy_->send_gatt_services_done(this->address_);
this->release_services();
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
this->release_services();
}
return;
}
@@ -375,19 +364,10 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
// This prevents race condition where we mark slot as free before controller cleanup is complete
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.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);
this->reset_connection_(param->disconnect.reason);
break;
}
case ESP_GATTC_CLOSE_EVT: {
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.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);
break;
}

View File

@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
class BluetoothProxy;
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
public:
void dump_config() override;
void loop() override;

View File

@@ -24,9 +24,6 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = 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) {
if (this->api_connection_ != nullptr) {
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.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: 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);
}
@@ -189,12 +183,6 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
this->send_device_connection(msg.address, false);
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 ||
connection->state() == espbt::ClientState::ESTABLISHED) {
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);
this->log_connection_info_(connection, "v3 without cache");
}
uint64_to_bd_addr(msg.address, connection->remote_bda_);
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED);
if (msg.has_address_type) {
uint64_to_bd_addr(msg.address, connection->remote_bda_);
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();
break;
}

View File

@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
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_
public:
BluetoothProxy();
@@ -161,8 +161,7 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
// Group 4: 1-byte types grouped together
bool active_;
uint8_t connection_count_{0};
bool configured_scan_active_{false}; // Configured scan mode from YAML
// 3 bytes used, 1 byte padding
// 2 bytes used, 2 bytes padding
};
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -7,8 +7,6 @@
#include <esphome/components/sensor/sensor.h>
#include <esphome/core/component.h>
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
namespace esphome {
namespace bme280_base {
@@ -100,18 +98,18 @@ void BME280Component::setup() {
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
if (chip_id != 0x60) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
this->mark_failed();
return;
}
// Send a soft reset.
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
this->mark_failed("Reset failed");
this->mark_failed();
return;
}
// Wait until the NVM data has finished loading.
@@ -120,12 +118,14 @@ void BME280Component::setup() {
do { // NOLINT
delay(2);
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
ESP_LOGW(TAG, "Error reading status register.");
this->mark_failed();
return;
}
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
if (status & BME280_STATUS_IM_UPDATE) {
this->mark_failed("Timeout loading NVM");
ESP_LOGW(TAG, "Timeout loading NVM.");
this->mark_failed();
return;
}
@@ -153,26 +153,26 @@ void BME280Component::setup() {
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
this->mark_failed("Read humidity control");
this->mark_failed();
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
this->mark_failed("Write humidity control");
this->mark_failed();
return;
}
uint8_t config_register = 0;
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
this->mark_failed();
return;
}
config_register &= ~0b11111100;
config_register |= 0b101 << 5; // 1000 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
this->mark_failed();
return;
}
}
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
break;
case NONE:
default:
@@ -223,21 +223,21 @@ void BME280Component::update() {
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers");
ESP_LOGW(TAG, "Error reading registers.");
this->status_set_warning();
return;
}
int32_t t_fine = 0;
float const temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature");
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
this->status_set_warning();
return;
}
float const pressure = this->read_pressure_(data, t_fine);
float const humidity = this->read_humidity_(data, t_fine);
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)

View File

@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
uint8_t raw_data[len * 2];
// 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) {
return err;
}

View File

@@ -2,8 +2,6 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#define BMP280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
namespace esphome {
namespace bmp280_base {
@@ -63,25 +61,25 @@ void BMP280Component::setup() {
// 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
if (!this->bmp_read_byte(0xD0, &chip_id)) {
if (!this->read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
if (!this->bmp_read_byte(0xD0, &chip_id)) {
if (!this->read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
if (chip_id != 0x58) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
this->mark_failed();
return;
}
// Send a soft reset.
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed("Reset failed");
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed();
return;
}
// Wait until the NVM data has finished loading.
@@ -89,13 +87,15 @@ void BMP280Component::setup() {
uint8_t retry = 5;
do {
delay(2);
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
ESP_LOGW(TAG, "Error reading status register.");
this->mark_failed();
return;
}
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
if (status & BMP280_STATUS_IM_UPDATE) {
this->mark_failed("Timeout loading NVM");
ESP_LOGW(TAG, "Timeout loading NVM.");
this->mark_failed();
return;
}
@@ -115,15 +115,15 @@ void BMP280Component::setup() {
this->calibration_.p9 = this->read_s16_le_(0x9E);
uint8_t config_register = 0;
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed();
return;
}
config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed();
return;
}
}
@@ -134,7 +134,7 @@ void BMP280Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, BMP280_ERROR_WRONG_CHIP_ID);
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
break;
case NONE:
default:
@@ -159,7 +159,7 @@ void BMP280Component::update() {
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
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();
return;
}
@@ -172,13 +172,13 @@ void BMP280Component::update() {
int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature");
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
this->status_set_warning();
return;
}
float pressure = this->read_pressure_(t_fine);
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa", temperature, pressure);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
@@ -188,10 +188,9 @@ void BMP280Component::update() {
}
float BMP280Component::read_temperature_(int32_t *t_fine) {
uint8_t data[3]{};
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
uint8_t data[3];
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
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);
adc >>= 4;
if (adc == 0x80000) {
@@ -213,7 +212,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
float BMP280Component::read_pressure_(int32_t t_fine) {
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;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
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; }
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
uint8_t data = 0;
this->bmp_read_byte(a_register, &data);
this->read_byte(a_register, &data);
return data;
}
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
uint16_t data = 0;
this->bmp_read_byte_16(a_register, &data);
this->read_byte_16(a_register, &data);
return (data >> 8) | (data << 8);
}
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }

View File

@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
float get_setup_priority() const override;
void update() override;
protected:
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool 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 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.
float read_temperature_(int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value.

View File

@@ -5,6 +5,19 @@
namespace esphome {
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() {
LOG_I2C_DEVICE(this);
BMP280Component::dump_config();

View File

@@ -11,12 +11,10 @@ static const char *const TAG = "bmp280_i2c.sensor";
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
public:
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
return read_bytes(a_register, data, len);
}
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
void dump_config() override;
};

View File

@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// 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->transfer_byte(set_bit(a_register, 7));
*data = this->transfer_byte(0);
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
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->transfer_byte(clear_bit(a_register, 7));
this->transfer_byte(data);
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
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->transfer_byte(set_bit(a_register, 7));
this->read_array(data, len);
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_
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->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0);

View File

@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
void setup() override;
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
bool bmp_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 bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
};
} // namespace bmp280_spi

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_RESTART,
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.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)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)

View File

@@ -6,19 +6,6 @@ namespace 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() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();

View File

@@ -7,10 +7,13 @@
namespace esphome {
namespace button {
class Button;
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
#define LOG_BUTTON(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}
#define SUB_BUTTON(name) \
protected: \

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -15,26 +15,6 @@ namespace camera {
*/
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.
* Encapsulates the JPEG encoded data and it is shared among
* all connected clients.
@@ -63,29 +43,6 @@ class 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.
* 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available.

View File

@@ -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

View File

@@ -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]))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -10,7 +10,7 @@ from esphome.const import (
PLATFORM_LN882X,
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"]
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):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])

View File

@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
ESP_LOGCONFIG(TAG, "CCS811");
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
if (this->baseline_) {
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);

View File

@@ -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.
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) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
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 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) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
return 0;

View File

@@ -47,7 +47,7 @@ from esphome.const import (
CONF_VISUAL,
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.cpp_generator import MockObjClass
@@ -517,6 +517,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(climate_ns.using)

View File

@@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
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);
ClimateDeviceRestoreState recovered{};
if (!this->rtc_.load(&recovered))

View File

@@ -32,7 +32,7 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
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.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)
async def cover_toggle_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
def cover_toggle_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
@@ -263,6 +263,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(cover_ns.using)

View File

@@ -194,7 +194,7 @@ void Cover::publish_state(bool save) {
}
}
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{};
if (!this->rtc_.load(&recovered))
return {};

View File

@@ -19,8 +19,8 @@ const extern float COVER_CLOSED;
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!(obj)->get_device_class_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}

View File

@@ -21,7 +21,7 @@ from esphome.const import (
CONF_WEB_SERVER,
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.cpp_generator import MockObjClass
@@ -172,7 +172,7 @@ async def new_datetime(config, *args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(datetime_ns.using)

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATE(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(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()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(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()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_TIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(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()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -1,5 +1,4 @@
#ifdef USE_ESP32
#include "soc/soc_caps.h"
#include "driver/gpio.h"
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
@@ -84,11 +83,7 @@ void DeepSleepComponent::deep_sleep_() {
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
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();
#endif
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
@@ -125,11 +120,7 @@ void DeepSleepComponent::deep_sleep_() {
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
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();
#endif
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;

View File

@@ -23,7 +23,7 @@ void DelonghiClimate::transmit_state() {
data->mark(DELONGHI_HEADER_MARK);
data->space(DELONGHI_HEADER_SPACE);
for (unsigned char b : remote_state) {
for (uint8_t b : remote_state) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DELONGHI_BIT_MARK);
bool bit = b & mask;

View File

@@ -15,7 +15,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
SCHEDULER_DONT_RUN,
)
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
IS_PLATFORM_COMPONENT = True
@@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
DisplayPageShowNextAction,
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,
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
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View File

@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
uint32_t seconds = 0;
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);
}

View File

@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
crc16 = calc_crc16_(address, 6);
address[5] = crc16 & 0xFF;
address[6] = (crc16 >> 8) & 0xFF;
this->write(address, 7);
this->write(address, 7, true);
}
float EE895Component::read_float_() {

View File

@@ -40,7 +40,6 @@ from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType
from esphome.writer import clean_cmake_cache
from .boards import BOARDS, STANDARD_BOARDS
from .const import ( # noqa
@@ -825,9 +824,8 @@ async def to_code(config):
cg.set_cpp_standard("gnu++20")
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
variant = config[CONF_VARIANT]
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
cg.add_define(ThreadModel.MULTI_ATOMICS)
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]:
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(
"post",
"post_build.py",
@@ -859,7 +854,11 @@ async def to_code(config):
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(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
@@ -1078,11 +1077,7 @@ def _write_idf_component_yml():
contents = yaml_util.dump({"dependencies": dependencies})
else:
contents = ""
if 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()
write_file_if_changed(yml_path, contents)
# Called by writer.py

View File

@@ -8,7 +8,6 @@
#include <cinttypes>
#include <vector>
#include <string>
#include <memory>
namespace esphome {
namespace esp32 {
@@ -157,23 +156,20 @@ class ESP32Preferences : public ESPPreferences {
return failed == 0;
}
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
NVSData stored_data{};
size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
return true;
}
// Check size first before allocating memory
if (actual_len != to_save.data.size()) {
return true;
}
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
stored_data.data.resize(actual_len);
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
return true;
}
return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
return to_save.data != stored_data.data;
}
bool reset() override {

View File

@@ -5,14 +5,9 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import (
CONF_ENABLE_ON_BOOT,
CONF_ESPHOME,
CONF_ID,
CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX,
)
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
from esphome.core import CORE, TimePeriod
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
@@ -285,10 +280,6 @@ async def to_code(config):
add_idf_sdkconfig_option(
"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
# This controls how many BLE characteristics can have notifications enabled
@@ -303,7 +294,6 @@ async def to_code(config):
if config[CONF_ADVERTISING]:
cg.add_define("USE_ESP32_BLE_ADVERTISING")
cg.add_define("USE_ESP32_BLE_UUID")
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))

View File

@@ -306,7 +306,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTS: {
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_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);
for (auto *gatts_handler : this->gatts_event_handlers_) {
gatts_handler->gatts_event_handler(event, gatts_if, param);
@@ -316,7 +316,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTC: {
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_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);
for (auto *gattc_handler : this->gattc_event_handlers_) {
gattc_handler->gattc_event_handler(event, gattc_if, param);

View File

@@ -3,7 +3,8 @@
#ifdef USE_ESP32
#include <cstddef> // for offsetof
#include <cstring> // for memcpy
#include <vector>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
@@ -61,24 +62,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),
"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().
// This class stores each event with minimal memory usage.
// 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.
// GAP events (99% of traffic) don't have the vector overhead.
// GATTC/GATTS events use heap allocation for their param and data.
//
// Event flow:
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
@@ -125,21 +112,21 @@ class BLEEvent {
this->init_gap_data_(e, p);
}
// Constructor for GATTC events - param stored inline, data may use heap
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// is only valid during the callback execution. Since BLE events are processed
// asynchronously in the main loop, we store our own copy inline to ensure
// the data remains valid until the event is processed.
// Constructor for GATTC events - uses heap allocation
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
// The param pointer from ESP-IDF is only valid during the callback execution.
// Since BLE events are processed asynchronously in the main loop, we must create
// 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) {
this->type_ = GATTC;
this->init_gattc_data_(e, i, p);
}
// Constructor for GATTS events - param stored inline, data may use heap
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// is only valid during the callback execution. Since BLE events are processed
// asynchronously in the main loop, we store our own copy inline to ensure
// the data remains valid until the event is processed.
// Constructor for GATTS events - uses heap allocation
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
// The param pointer from ESP-IDF is only valid during the callback execution.
// Since BLE events are processed asynchronously in the main loop, we must create
// 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) {
this->type_ = GATTS;
this->init_gatts_data_(e, i, p);
@@ -149,32 +136,25 @@ class BLEEvent {
~BLEEvent() { this->release(); }
// 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
void release() {
switch (this->type_) {
case GAP:
// GAP events don't have heap allocations
break;
case GATTC:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
delete[] this->event_.gattc.data.heap_data;
}
// Clear critical fields to prevent issues if type changes
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
break;
case GATTS:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
delete[] this->event_.gatts.data.heap_data;
}
// Clear critical fields to prevent issues if type changes
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
break;
if (this->type_ == GAP) {
return;
}
if (this->type_ == GATTC) {
delete this->event_.gattc.gattc_param;
delete this->event_.gattc.data;
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return;
}
if (this->type_ == GATTS) {
delete this->event_.gatts.gatts_param;
delete this->event_.gatts.data;
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
}
}
@@ -226,30 +206,20 @@ class BLEEvent {
// NOLINTNEXTLINE(readability-identifier-naming)
struct gattc_event {
esp_ble_gattc_cb_param_t gattc_param; // Stored inline (28 bytes)
esp_gattc_cb_event_t gattc_event; // 4 bytes
union {
uint8_t *heap_data; // 4 bytes when heap-allocated
uint8_t inline_data[GATTC_INLINE_DATA_SIZE]; // 44 bytes when stored inline
} data; // 44 bytes total
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
esp_gattc_cb_event_t gattc_event;
esp_gatt_if_t gattc_if;
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
std::vector<uint8_t> *data; // Heap-allocated
} gattc; // 16 bytes (pointers only)
// NOLINTNEXTLINE(readability-identifier-naming)
struct gatts_event {
esp_ble_gatts_cb_param_t gatts_param; // Stored inline (32 bytes)
esp_gatts_cb_event_t gatts_event; // 4 bytes
union {
uint8_t *heap_data; // 4 bytes when heap-allocated
uint8_t inline_data[GATTS_INLINE_DATA_SIZE]; // 40 bytes when stored inline
} data; // 40 bytes total
uint16_t data_len; // 2 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
esp_gatts_cb_event_t gatts_event;
esp_gatt_if_t gatts_if;
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
std::vector<uint8_t> *data; // Heap-allocated
} gatts; // 16 bytes (pointers only)
} event_; // 80 bytes
ble_event_t type_;
@@ -263,29 +233,6 @@ class BLEEvent {
const esp_ble_sec_t &security() const { return event_.gap.security; }
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
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->event_.gap.gap_event = e;
@@ -370,38 +317,35 @@ class BLEEvent {
this->event_.gattc.gattc_if = i;
if (p == nullptr) {
// Zero out the param struct when null
memset(&this->event_.gattc.gattc_param, 0, sizeof(this->event_.gattc.gattc_param));
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0;
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Copy param struct inline (no heap allocation!)
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// along with small data payloads, eliminating all heap allocations for typical BLE operations
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// is only valid during the callback and will be reused/freed after we return
this->event_.gattc.gattc_param = *p;
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
// IMPORTANT: This heap allocation provides clear ownership semantics:
// - The BLEEvent owns the allocated memory for its lifetime
// - 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
// 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.
switch (e) {
case ESP_GATTC_NOTIFY_EVT:
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
this->event_.gattc, p->notify.value, p->notify.value_len, &this->event_.gattc.gattc_param.notify.value);
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
break;
case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT:
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
this->event_.gattc, p->read.value, p->read.value_len, &this->event_.gattc.gattc_param.read.value);
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
break;
default:
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0;
this->event_.gattc.data = nullptr;
break;
}
}
@@ -412,33 +356,30 @@ class BLEEvent {
this->event_.gatts.gatts_if = i;
if (p == nullptr) {
// Zero out the param struct when null
memset(&this->event_.gatts.gatts_param, 0, sizeof(this->event_.gatts.gatts_param));
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0;
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
return; // Invalid event, but we can't log in header file
}
// Copy param struct inline (no heap allocation!)
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// along with small data payloads, eliminating all heap allocations for typical BLE operations
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// is only valid during the callback and will be reused/freed after we return
this->event_.gatts.gatts_param = *p;
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
// IMPORTANT: This heap allocation provides clear ownership semantics:
// - The BLEEvent owns the allocated memory for its lifetime
// - 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
// 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.
switch (e) {
case ESP_GATTS_WRITE_EVT:
copy_data_with_inline_storage_<decltype(this->event_.gatts), GATTS_INLINE_DATA_SIZE>(
this->event_.gatts, p->write.value, p->write.len, &this->event_.gatts.gatts_param.write.value);
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
break;
default:
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0;
this->event_.gatts.data = nullptr;
break;
}
}
@@ -448,15 +389,6 @@ class BLEEvent {
// 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");
// 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
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");

View File

@@ -1,7 +1,6 @@
#include "ble_uuid.h"
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_UUID
#include <cstring>
#include <cstdio>
@@ -191,5 +190,4 @@ std::string ESPBTUUID::to_string() const {
} // namespace esphome::esp32_ble
#endif // USE_ESP32_BLE_UUID
#endif // USE_ESP32
#endif

View File

@@ -1,11 +1,9 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_UUID
#include <string>
#include <esp_bt_defs.h>
@@ -44,5 +42,4 @@ class ESPBTUUID {
} // namespace esphome::esp32_ble
#endif // USE_ESP32_BLE_UUID
#endif // USE_ESP32
#endif

View File

@@ -65,8 +65,6 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
async def to_code(config):
cg.add_define("USE_ESP32_BLE_UUID")
uuid = config[CONF_UUID].hex
uuid_arr = [
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)

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