Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
fb1c2467b9 [api] Optimize varint encoding performance for Bluetooth proxy efficiency 2025-08-17 23:55:17 -05:00
596 changed files with 6197 additions and 23357 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
@@ -105,7 +105,6 @@ jobs:
script/ci-custom.py
script/build_codeowners.py --check
script/build_language_schema.py --check
script/generate-esp32-boards.py --check
pytest:
name: Run pytest
@@ -157,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
@@ -218,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.13.0
rev: v0.12.9
hooks:
# Run the linter.
- id: ruff

View File

@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @dan-s-github @tobias-
esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
@@ -88,8 +88,7 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @bdraco @DT-art1
esphome/components/camera_encoder/* @DT-art1
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core
@@ -145,9 +144,9 @@ esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
esphome/components/esp32_ble_client/* @bdraco @jesserockz
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_ble_tracker/* @bdraco
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
@@ -167,7 +166,7 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
esphome/components/font/* @clydebarrow @esphome/core
esphome/components/fs3000/* @kahrendt
esphome/components/ft5x06/* @clydebarrow
@@ -203,7 +202,7 @@ esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34
@@ -228,13 +227,13 @@ esphome/components/iaqcore/* @yozik04
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core
esphome/components/ina226/* @latonita @Sergio303
esphome/components/ina226/* @Sergio303 @latonita
esphome/components/ina260/* @mreditor97
esphome/components/ina2xx_base/* @latonita
esphome/components/ina2xx_i2c/* @latonita
esphome/components/ina2xx_spi/* @latonita
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
@@ -277,8 +276,8 @@ esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
esphome/components/mcp23x08_base/* @jesserockz
esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
@@ -299,7 +298,6 @@ esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_rgb/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
@@ -343,7 +341,7 @@ esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pi4ioe5v6408/* @jesserockz
@@ -354,9 +352,9 @@ esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
esphome/components/pmsx003/* @ximex
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @jesserockz @OttoWinter
esphome/components/pn532_i2c/* @jesserockz @OttoWinter
esphome/components/pn532_spi/* @jesserockz @OttoWinter
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/pn7150/* @jesserockz @kbx81
esphome/components/pn7150_i2c/* @jesserockz @kbx81
esphome/components/pn7160/* @jesserockz @kbx81
@@ -365,7 +363,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/pylontech/* @functionpointer
esphome/components/qmp6988/* @andrewpc
@@ -406,7 +404,7 @@ esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @martgras @SenexCrenshaw
esphome/components/sgp4x/* @SenexCrenshaw @martgras
esphome/components/shelly_dimmer/* @edge90 @rnauber
esphome/components/sht3xd/* @mrtoy-me
esphome/components/sht4x/* @sjtrny

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.10.0-dev
PROJECT_NUMBER = 2025.9.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

@@ -15,11 +15,9 @@ import argcomplete
from esphome import const, writer, yaml_util
import esphome.codegen as cg
from esphome.components.mqtt import CONF_DISCOVER_IP
from esphome.config import iter_component_configs, read_config, strip_default_ids
from esphome.const import (
ALLOWED_NAME_CHARS,
CONF_API,
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
@@ -45,7 +43,6 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.enum import StrEnum
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.types import ConfigType
@@ -109,23 +106,13 @@ def choose_prompt(options, purpose: str = None):
return options[opt - 1][1]
class Purpose(StrEnum):
UPLOADING = "uploading"
LOGGING = "logging"
def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
"""Resolve an address using cache if available, otherwise return the address itself."""
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
_LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
return cached
return [address]
def choose_upload_log_host(
default: list[str] | str | None,
check_default: str | None,
purpose: Purpose,
show_ota: bool,
show_mqtt: bool,
show_api: bool,
purpose: str | None = None,
) -> list[str]:
# Convert to list for uniform handling
defaults = [default] if isinstance(default, str) else default or []
@@ -145,144 +132,53 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
# ensure IP adresses are used first
if is_ip_address(CORE.address) and (
(purpose == Purpose.LOGGING and has_api())
or (purpose == Purpose.UPLOADING and has_ota())
if (show_ota and "ota" in CORE.config) or (
show_api and "api" in CORE.config
):
resolved.extend(_resolve_with_cache(CORE.address, purpose))
if purpose == Purpose.LOGGING:
if has_api() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_mqtt_logging():
resolved.append("MQTT")
if has_api() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
elif purpose == Purpose.UPLOADING:
if has_ota() and has_mqtt_ip_lookup():
resolved.append("MQTTIP")
if has_ota() and has_non_ip_address():
resolved.extend(_resolve_with_cache(CORE.address, purpose))
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
options = [
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
]
if purpose == Purpose.LOGGING:
if has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
elif purpose == Purpose.UPLOADING and has_ota():
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address))
if show_mqtt and has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if check_default is not None and check_default in [opt[1] for opt in options]:
return [check_default]
return [choose_prompt(options, purpose=purpose)]
def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
if CONF_MQTT not in CORE.config:
return False
mqtt_config = CORE.config[CONF_MQTT]
# enabled by default
if CONF_LOG_TOPIC not in mqtt_config:
return True
def mqtt_logging_enabled(mqtt_config):
log_topic = mqtt_config[CONF_LOG_TOPIC]
if log_topic is None:
return False
if CONF_TOPIC not in log_topic:
return False
return log_topic.get(CONF_LEVEL, None) != "NONE"
def has_mqtt() -> bool:
"""Check if MQTT is available."""
return CONF_MQTT in CORE.config
def has_api() -> bool:
"""Check if API is available."""
return CONF_API in CORE.config
def has_ota() -> bool:
"""Check if OTA is available."""
return CONF_OTA in CORE.config
def has_mqtt_ip_lookup() -> bool:
"""Check if MQTT is available and IP lookup is supported."""
if CONF_MQTT not in CORE.config:
return False
# Default Enabled
if CONF_DISCOVER_IP not in CORE.config[CONF_MQTT]:
return True
return CORE.config[CONF_MQTT][CONF_DISCOVER_IP]
def has_mdns() -> bool:
"""Check if MDNS is available."""
return CONF_MDNS not in CORE.config or not CORE.config[CONF_MDNS][CONF_DISABLED]
def has_non_ip_address() -> bool:
"""Check if CORE.address is set and is not an IP address."""
return CORE.address is not None and not is_ip_address(CORE.address)
def has_ip_address() -> bool:
"""Check if CORE.address is a valid IP address."""
return CORE.address is not None and is_ip_address(CORE.address)
def has_resolvable_address() -> bool:
"""Check if CORE.address is resolvable (via mDNS or is an IP address)."""
return has_mdns() or has_ip_address()
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
from esphome import mqtt
return mqtt.get_esphome_device_ip(config, username, password, client_id)
_PORT_TO_PORT_TYPE = {
"MQTT": "MQTT",
"MQTTIP": "MQTTIP",
}
def has_mqtt_logging() -> bool:
"""Check if MQTT logging is available."""
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
mqtt_config
)
def get_port_type(port: str) -> str:
if port.startswith("/") or port.startswith("COM"):
return "SERIAL"
return _PORT_TO_PORT_TYPE.get(port, "NETWORK")
if port == "MQTT":
return "MQTT"
return "NETWORK"
def run_miniterm(config: ConfigType, port: str, args) -> int:
@@ -327,9 +223,7 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time_ = datetime.now()
nanoseconds = time_.microsecond // 1000
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
time_str = datetime.now().time().strftime("[%H:%M:%S]")
safe_print(parser.parse_line(line, time_str))
backtrace_state = platformio_api.process_stacktrace(
@@ -499,29 +393,27 @@ def check_permissions(port: str):
)
def upload_program(
config: ConfigType, args: ArgsProtocol, devices: list[str]
) -> tuple[int, str | None]:
host = devices[0]
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "upload_program")(config, args, host):
return 0, host
return 0
except AttributeError:
pass
if get_port_type(host) == "SERIAL":
check_permissions(host)
exit_code = 1
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
file = getattr(args, "file", None)
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
exit_code = upload_using_platformio(config, host)
# else: Unknown target platform, exit_code remains 1
return upload_using_esptool(config, host, file, args.upload_speed)
return exit_code, host if exit_code == 0 else None
if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, host)
if CORE.is_libretiny:
return upload_using_platformio(config, host)
return 1 # Unknown target platform
ota_conf = {}
for ota_item in config.get(CONF_OTA, []):
@@ -538,23 +430,31 @@ def upload_program(
remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "")
binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
# MQTT address resolution
if get_port_type(host) in ("MQTT", "MQTTIP"):
devices = mqtt_get_ip(config, args.username, args.password, args.client_id)
# Check if we should use MQTT for address resolution
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
devices: list[str] = args.device or []
if (
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
and (not devices or host in ("MQTT", "OTA"))
and (
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
or get_port_type(host) == "MQTT"
)
):
from esphome import mqtt
return espota2.run_ota(devices, remote_port, password, binary)
host = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
if getattr(args, "file", None) is not None:
return espota2.run_ota(host, remote_port, password, args.file)
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
try:
module = importlib.import_module("esphome.components." + CORE.target_platform)
if getattr(module, "show_logs")(config, args, devices):
return 0
except AttributeError:
pass
if "logger" not in config:
raise EsphomeError("Logger is not configured!")
@@ -563,28 +463,20 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
if get_port_type(port) == "SERIAL":
check_permissions(port)
return run_miniterm(config, port, args)
if get_port_type(port) == "NETWORK" and "api" in config:
addresses_to_use = devices
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
from esphome import mqtt
port_type = get_port_type(port)
# Check if we should use API for logging
if has_api():
addresses_to_use: list[str] | None = None
if port_type == "NETWORK" and (has_mdns() or is_ip_address(port)):
addresses_to_use = devices
elif port_type in ("NETWORK", "MQTT", "MQTTIP") and has_mqtt_ip_lookup():
# Only use MQTT IP lookup if the first condition didn't match
# (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
addresses_to_use = mqtt_get_ip(
mqtt_address = mqtt.get_esphome_device_ip(
config, args.username, args.password, args.client_id
)
)[0]
addresses_to_use = [mqtt_address]
if addresses_to_use is not None:
from esphome.components.api.client import run_logs
from esphome.components.api.client import run_logs
return run_logs(config, addresses_to_use)
if port_type in ("NETWORK", "MQTT") and has_mqtt_logging():
return run_logs(config, addresses_to_use)
if get_port_type(port) == "MQTT" and "mqtt" in config:
from esphome import mqtt
return mqtt.show_logs(
@@ -632,13 +524,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -657,14 +542,23 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
purpose=Purpose.UPLOADING,
show_ota=True,
show_mqtt=False,
show_api=False,
purpose="uploading",
)
exit_code, _ = upload_program(config, args, devices)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
else:
_LOGGER.warning("Failed to upload to %s", devices)
# Try each device until one succeeds
exit_code = 1
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
return 0
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
return exit_code
@@ -682,7 +576,10 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
purpose=Purpose.LOGGING,
show_ota=False,
show_mqtt=True,
show_api=True,
purpose="logging",
)
return show_logs(config, args, devices)
@@ -708,14 +605,25 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=args.device,
check_default=None,
purpose=Purpose.UPLOADING,
show_ota=True,
show_mqtt=False,
show_api=True,
purpose="uploading",
)
exit_code, successful_device = upload_program(config, args, devices)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
else:
_LOGGER.warning("Failed to upload to %s", devices)
# Try each device for upload until one succeeds
successful_device: str | None = None
for device in devices:
_LOGGER.info("Uploading to %s", device)
exit_code = upload_program(config, args, device)
if exit_code == 0:
_LOGGER.info("Successfully uploaded program.")
successful_device = device
break
if len(devices) > 1:
_LOGGER.warning("Failed to upload to %s", device)
if successful_device is None:
return exit_code
if args.no_logs:
@@ -725,7 +633,10 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
devices = choose_upload_log_host(
default=successful_device,
check_default=successful_device,
purpose=Purpose.LOGGING,
show_ota=False,
show_mqtt=True,
show_api=True,
purpose="logging",
)
return show_logs(config, args, devices)
@@ -979,18 +890,6 @@ def parse_args(argv):
help="Add a substitution",
metavar=("key", "value"),
)
options_parser.add_argument(
"--mdns-address-cache",
help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
action="append",
default=[],
)
options_parser.add_argument(
"--dns-address-cache",
help="DNS address cache mapping in format 'hostname=ip1,ip2'",
action="append",
default=[],
)
parser = argparse.ArgumentParser(
description=f"ESPHome {const.__version__}", parents=[options_parser]
@@ -1035,17 +934,6 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",
@@ -1249,15 +1137,9 @@ def parse_args(argv):
def run_esphome(argv):
from esphome.address_cache import AddressCache
args = parse_args(argv)
CORE.dashboard = args.dashboard
# Create address cache from command-line arguments
CORE.address_cache = AddressCache.from_cli_args(
args.mdns_address_cache, args.dns_address_cache
)
# Override log level if verbose is set
if args.verbose:
args.log_level = "DEBUG"
@@ -1287,12 +1169,7 @@ def run_esphome(argv):
CORE.config_path = conf_path
CORE.dashboard = args.dashboard
# For logs command, skip updating external components
skip_external = args.command == "logs"
config = read_config(
dict(args.substitution) if args.substitution else {},
skip_external_update=skip_external,
)
config = read_config(dict(args.substitution) if args.substitution else {})
if config is None:
return 2
CORE.config = config

View File

@@ -1,142 +0,0 @@
"""Address cache for DNS and mDNS lookups."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Iterable
_LOGGER = logging.getLogger(__name__)
def normalize_hostname(hostname: str) -> str:
"""Normalize hostname for cache lookups.
Removes trailing dots and converts to lowercase.
"""
return hostname.rstrip(".").lower()
class AddressCache:
"""Cache for DNS and mDNS address lookups.
This cache stores pre-resolved addresses from command-line arguments
to avoid slow DNS/mDNS lookups during builds.
"""
def __init__(
self,
mdns_cache: dict[str, list[str]] | None = None,
dns_cache: dict[str, list[str]] | None = None,
) -> None:
"""Initialize the address cache.
Args:
mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
dns_cache: Pre-populated DNS addresses (hostname -> IPs)
"""
self.mdns_cache = mdns_cache or {}
self.dns_cache = dns_cache or {}
def _get_cached_addresses(
self, hostname: str, cache: dict[str, list[str]], cache_type: str
) -> list[str] | None:
"""Get cached addresses from a specific cache.
Args:
hostname: The hostname to look up
cache: The cache dictionary to check
cache_type: Type of cache for logging ("mDNS" or "DNS")
Returns:
List of IP addresses if found in cache, None otherwise
"""
normalized = normalize_hostname(hostname)
if addresses := cache.get(normalized):
_LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
return addresses
return None
def get_mdns_addresses(self, hostname: str) -> list[str] | None:
"""Get cached mDNS addresses for a hostname.
Args:
hostname: The hostname to look up (should end with .local)
Returns:
List of IP addresses if found in cache, None otherwise
"""
return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
def get_dns_addresses(self, hostname: str) -> list[str] | None:
"""Get cached DNS addresses for a hostname.
Args:
hostname: The hostname to look up
Returns:
List of IP addresses if found in cache, None otherwise
"""
return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
def get_addresses(self, hostname: str) -> list[str] | None:
"""Get cached addresses for a hostname.
Checks mDNS cache for .local domains, DNS cache otherwise.
Args:
hostname: The hostname to look up
Returns:
List of IP addresses if found in cache, None otherwise
"""
normalized = normalize_hostname(hostname)
if normalized.endswith(".local"):
return self.get_mdns_addresses(hostname)
return self.get_dns_addresses(hostname)
def has_cache(self) -> bool:
"""Check if any cache entries exist."""
return bool(self.mdns_cache or self.dns_cache)
@classmethod
def from_cli_args(
cls, mdns_args: Iterable[str], dns_args: Iterable[str]
) -> AddressCache:
"""Create cache from command-line arguments.
Args:
mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
dns_args: List of DNS cache entries like ['host=ip1,ip2']
Returns:
Configured AddressCache instance
"""
mdns_cache = cls._parse_cache_args(mdns_args)
dns_cache = cls._parse_cache_args(dns_args)
return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
@staticmethod
def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
"""Parse cache arguments into a dictionary.
Args:
cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
Returns:
Dictionary mapping normalized hostnames to list of IP addresses
"""
cache: dict[str, list[str]] = {}
for arg in cache_args:
if "=" not in arg:
_LOGGER.warning(
"Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
)
continue
hostname, ips = arg.split("=", 1)
# Normalize hostname for consistent lookups
normalized = normalize_hostname(hostname)
cache[normalized] = [ip.strip() for ip in ips.split(",")]
return cache

File diff suppressed because it is too large Load Diff

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(LOG_STR("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

@@ -11,8 +11,15 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.const import (
CONF_ANALOG,
CONF_INPUT,
CONF_NUMBER,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@@ -266,3 +273,21 @@ def validate_adc_pin(value):
)(value)
raise NotImplementedError
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -241,8 +241,6 @@ float ADCSensor::sample_autorange_() {
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#else
adc_cali_line_fitting_config_t cali_config = {
.unit_id = this->adc_unit_,
@@ -253,14 +251,10 @@ float ADCSensor::sample_autorange_() {
#endif
};
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
#endif
int raw;
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
if (err != ESP_OK) {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
@@ -281,10 +275,8 @@ float ADCSensor::sample_autorange_() {
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
if (err == ESP_OK) {
voltage = voltage_mv / 1000.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
} else {
voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
}
// Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
@@ -295,7 +287,6 @@ float ADCSensor::sample_autorange_() {
#endif
} else {
voltage = raw * 3.3f / 4095.0f;
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
}
return {raw, voltage};
@@ -333,32 +324,18 @@ float ADCSensor::sample_autorange_() {
}
const int adc_half = 2048;
const uint32_t c12 = std::min(raw12, adc_half);
const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
const uint32_t c0 = std::min(4095 - raw0, adc_half);
const uint32_t csum = c12 + c6 + c2 + c0;
ESP_LOGVV(TAG, "Autorange summary:");
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
uint32_t c12 = std::min(raw12, adc_half);
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
uint32_t c0 = std::min(4095 - raw0, adc_half);
uint32_t csum = c12 + c6 + c2 + c0;
if (csum == 0) {
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
return NAN;
}
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
c0, csum, final_result);
return final_result;
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
}
} // namespace adc

View File

@@ -9,7 +9,6 @@ from esphome.components.zephyr import (
zephyr_add_prj_conf,
zephyr_add_user,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ATTENUATION,
@@ -21,7 +20,6 @@ from esphome.const import (
PLATFORM_NRF52,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
PlatformFramework,
)
from esphome.core import CORE
@@ -176,21 +174,3 @@ async def to_code(config):
}};
"""
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)

View File

@@ -113,7 +113,7 @@ void ADE7880::update() {
if (this->channel_a_ != nullptr) {
auto *chan = this->channel_a_;
this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
this->update_sensor_from_s24zp_register16_(chan->voltage, AVRMS, [](float val) { return val / 10000.0f; });
this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; });
this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
this->update_sensor_from_s16_register16_(chan->power_factor, APF,

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

@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning(LOG_STR("Read failed, will retry"));
this->status_set_warning("Read failed, will retry");
this->restart_read_();
return;
}
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
} else {
ESP_LOGD(TAG, "Invalid humidity, retrying");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
}
this->restart_read_();
return;
@@ -144,7 +144,7 @@ void AHT10Component::update() {
return;
this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
return;
}
this->restart_read_();

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

@@ -7,7 +7,7 @@ service APIConnection {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
rpc connect (ConnectRequest) returns (ConnectResponse) {
option (needs_setup_connection) = false;
option (needs_authentication) = false;
}
@@ -27,6 +27,9 @@ service APIConnection {
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
option (needs_authentication) = false;
}
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
@@ -129,11 +132,10 @@ message HelloResponse {
// Message sent at the beginning of each connection to authenticate the client
// Can only be sent by the client and only at the beginning of the connection
message AuthenticationRequest {
message ConnectRequest {
option (id) = 3;
option (source) = SOURCE_CLIENT;
option (no_delay) = true;
option (ifdef) = "USE_API_PASSWORD";
// The password to log in with
string password = 1;
@@ -141,11 +143,10 @@ message AuthenticationRequest {
// Confirmation of successful connection. After this the connection is available for all traffic.
// Can only be sent by the server and only at the beginning of the connection
message AuthenticationResponse {
message ConnectResponse {
option (id) = 4;
option (source) = SOURCE_SERVER;
option (no_delay) = true;
option (ifdef) = "USE_API_PASSWORD";
bool invalid_password = 1;
}
@@ -808,16 +809,15 @@ message HomeAssistantStateResponse {
// ==================== IMPORT TIME ====================
message GetTimeRequest {
option (id) = 36;
option (source) = SOURCE_SERVER;
option (source) = SOURCE_BOTH;
}
message GetTimeResponse {
option (id) = 37;
option (source) = SOURCE_CLIENT;
option (source) = SOURCE_BOTH;
option (no_delay) = true;
fixed32 epoch_seconds = 1;
string timezone = 2;
}
// ==================== USER-DEFINES SERVICES ====================
@@ -1712,7 +1712,6 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
}
message BluetoothScannerSetModeRequest {

View File

@@ -42,8 +42,6 @@ static constexpr uint8_t MAX_PING_RETRIES = 60;
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
static const char *const TAG = "api.connection";
#ifdef USE_CAMERA
static const int CAMERA_STOP_STREAM = 5000;
@@ -114,7 +112,7 @@ void APIConnection::start() {
APIError err = this->helper_->init();
if (err != APIError::OK) {
on_fatal_error();
this->log_warning_(LOG_STR("Helper init failed"), err);
this->log_warning_("Helper init failed", err);
return;
}
this->client_info_.peername = helper_->getpeername();
@@ -161,7 +159,7 @@ void APIConnection::loop() {
break;
} else if (err != APIError::OK) {
on_fatal_error();
this->log_warning_(LOG_STR("Reading failed"), err);
this->log_warning_("Reading failed", err);
return;
} else {
this->last_traffic_ = now;
@@ -467,7 +465,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);
}
@@ -1072,17 +1072,17 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
#ifdef USE_HOMEASSISTANT_TIME
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) {
if (homeassistant::global_homeassistant_time != nullptr)
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (!value.timezone.empty() && value.timezone != homeassistant::global_homeassistant_time->get_timezone()) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone);
}
#endif
}
}
#endif
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
GetTimeResponse resp;
resp.epoch_seconds = ::time(nullptr);
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
}
#ifdef USE_BLUETOOTH_PROXY
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
@@ -1372,8 +1372,9 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 12;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.set_server_info(ESPHOME_VERSION_REF);
// Temporary string for concatenation - will be valid during send_message call
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.set_server_info(StringRef(server_info));
resp.set_name(StringRef(App.get_name()));
#ifdef USE_API_PASSWORD
@@ -1386,17 +1387,20 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
}
bool APIConnection::send_connect_response(const ConnectRequest &msg) {
bool correct = true;
#ifdef USE_API_PASSWORD
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp;
correct = this->parent_->check_password(msg.password);
#endif
ConnectResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password);
if (!resp.invalid_password) {
resp.invalid_password = !correct;
if (correct) {
this->complete_authentication_();
}
return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
}
#endif // USE_API_PASSWORD
bool APIConnection::send_ping_response(const PingRequest &msg) {
PingResponse resp;
@@ -1417,9 +1421,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
std::string mac_address = get_mac_address_pretty();
resp.set_mac_address(StringRef(mac_address));
// Compile-time StringRef constants
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)
@@ -1561,7 +1569,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
return false;
if (err != APIError::OK) {
on_fatal_error();
this->log_warning_(LOG_STR("Packet write failed"), err);
this->log_warning_("Packet write failed", err);
return false;
}
// Do not set last_traffic_ on send
@@ -1748,7 +1756,7 @@ void APIConnection::process_batch_() {
std::span<const PacketInfo>(packet_info, packet_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
on_fatal_error();
this->log_warning_(LOG_STR("Batch write failed"), err);
this->log_warning_("Batch write failed", err);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1826,14 +1834,11 @@ void APIConnection::process_state_subscriptions_() {
}
#endif // USE_API_HOMEASSISTANT_STATES
void APIConnection::log_warning_(const LogString *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message),
LOG_STR_ARG(api_error_to_logstr(err)), errno);
void APIConnection::log_warning_(const char *message, APIError err) {
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
}
void APIConnection::log_socket_operation_failed_(APIError err) {
this->log_warning_(LOG_STR("Socket operation failed"), err);
}
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
} // namespace esphome::api
#endif

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;
@@ -197,9 +197,7 @@ class APIConnection final : public APIServerConnection {
void on_get_time_response(const GetTimeResponse &value) override;
#endif
bool send_hello_response(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
bool send_authenticate_response(const AuthenticationRequest &msg) override;
#endif
bool send_connect_response(const ConnectRequest &msg) override;
bool send_disconnect_response(const DisconnectRequest &msg) override;
bool send_ping_response(const PingRequest &msg) override;
bool send_device_info_response(const DeviceInfoRequest &msg) override;
@@ -221,6 +219,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
bool send_get_time_response(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override;
#endif
@@ -302,17 +301,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());
@@ -733,7 +724,7 @@ class APIConnection final : public APIServerConnection {
}
// Helper function to log API errors with errno
void log_warning_(const LogString *message, APIError err);
void log_warning_(const char *message, APIError err);
// Specific helper for duplicated error message
void log_socket_operation_failed_(APIError err);
};

View File

@@ -23,59 +23,59 @@ static const char *const TAG = "api.frame_helper";
#define LOG_PACKET_SENDING(data, len) ((void) 0)
#endif
const LogString *api_error_to_logstr(APIError err) {
const char *api_error_to_str(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) {
return LOG_STR("OK");
return "OK";
} else if (err == APIError::WOULD_BLOCK) {
return LOG_STR("WOULD_BLOCK");
return "WOULD_BLOCK";
} else if (err == APIError::BAD_INDICATOR) {
return LOG_STR("BAD_INDICATOR");
return "BAD_INDICATOR";
} else if (err == APIError::BAD_DATA_PACKET) {
return LOG_STR("BAD_DATA_PACKET");
return "BAD_DATA_PACKET";
} else if (err == APIError::TCP_NODELAY_FAILED) {
return LOG_STR("TCP_NODELAY_FAILED");
return "TCP_NODELAY_FAILED";
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
return LOG_STR("TCP_NONBLOCKING_FAILED");
return "TCP_NONBLOCKING_FAILED";
} else if (err == APIError::CLOSE_FAILED) {
return LOG_STR("CLOSE_FAILED");
return "CLOSE_FAILED";
} else if (err == APIError::SHUTDOWN_FAILED) {
return LOG_STR("SHUTDOWN_FAILED");
return "SHUTDOWN_FAILED";
} else if (err == APIError::BAD_STATE) {
return LOG_STR("BAD_STATE");
return "BAD_STATE";
} else if (err == APIError::BAD_ARG) {
return LOG_STR("BAD_ARG");
return "BAD_ARG";
} else if (err == APIError::SOCKET_READ_FAILED) {
return LOG_STR("SOCKET_READ_FAILED");
return "SOCKET_READ_FAILED";
} else if (err == APIError::SOCKET_WRITE_FAILED) {
return LOG_STR("SOCKET_WRITE_FAILED");
return "SOCKET_WRITE_FAILED";
} else if (err == APIError::OUT_OF_MEMORY) {
return LOG_STR("OUT_OF_MEMORY");
return "OUT_OF_MEMORY";
} else if (err == APIError::CONNECTION_CLOSED) {
return LOG_STR("CONNECTION_CLOSED");
return "CONNECTION_CLOSED";
}
#ifdef USE_API_NOISE
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
return "BAD_HANDSHAKE_PACKET_LEN";
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
return "HANDSHAKESTATE_READ_FAILED";
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
return "HANDSHAKESTATE_WRITE_FAILED";
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
return "HANDSHAKESTATE_BAD_STATE";
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
return "CIPHERSTATE_DECRYPT_FAILED";
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
return "CIPHERSTATE_ENCRYPT_FAILED";
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
return "HANDSHAKESTATE_SETUP_FAILED";
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
return "HANDSHAKESTATE_SPLIT_FAILED";
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
return "BAD_HANDSHAKE_ERROR_BYTE";
}
#endif
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
// Default implementation for loop - handles sending buffered data

View File

@@ -66,7 +66,7 @@ enum class APIError : uint16_t {
#endif
};
const LogString *api_error_to_logstr(APIError err);
const char *api_error_to_str(APIError err);
class APIFrameHelper {
public:
@@ -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

@@ -10,18 +10,10 @@
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
static const char *const TAG = "api.noise";
#ifdef USE_ESP8266
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
#else
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
#endif
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
@@ -35,42 +27,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
#endif
/// Convert a noise error code to a readable error
const LogString *noise_err_to_logstr(int err) {
std::string noise_err_to_str(int err) {
if (err == NOISE_ERROR_NO_MEMORY)
return LOG_STR("NO_MEMORY");
return "NO_MEMORY";
if (err == NOISE_ERROR_UNKNOWN_ID)
return LOG_STR("UNKNOWN_ID");
return "UNKNOWN_ID";
if (err == NOISE_ERROR_UNKNOWN_NAME)
return LOG_STR("UNKNOWN_NAME");
return "UNKNOWN_NAME";
if (err == NOISE_ERROR_MAC_FAILURE)
return LOG_STR("MAC_FAILURE");
return "MAC_FAILURE";
if (err == NOISE_ERROR_NOT_APPLICABLE)
return LOG_STR("NOT_APPLICABLE");
return "NOT_APPLICABLE";
if (err == NOISE_ERROR_SYSTEM)
return LOG_STR("SYSTEM");
return "SYSTEM";
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
return LOG_STR("REMOTE_KEY_REQUIRED");
return "REMOTE_KEY_REQUIRED";
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
return LOG_STR("LOCAL_KEY_REQUIRED");
return "LOCAL_KEY_REQUIRED";
if (err == NOISE_ERROR_PSK_REQUIRED)
return LOG_STR("PSK_REQUIRED");
return "PSK_REQUIRED";
if (err == NOISE_ERROR_INVALID_LENGTH)
return LOG_STR("INVALID_LENGTH");
return "INVALID_LENGTH";
if (err == NOISE_ERROR_INVALID_PARAM)
return LOG_STR("INVALID_PARAM");
return "INVALID_PARAM";
if (err == NOISE_ERROR_INVALID_STATE)
return LOG_STR("INVALID_STATE");
return "INVALID_STATE";
if (err == NOISE_ERROR_INVALID_NONCE)
return LOG_STR("INVALID_NONCE");
return "INVALID_NONCE";
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
return LOG_STR("INVALID_PRIVATE_KEY");
return "INVALID_PRIVATE_KEY";
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
return LOG_STR("INVALID_PUBLIC_KEY");
return "INVALID_PUBLIC_KEY";
if (err == NOISE_ERROR_INVALID_FORMAT)
return LOG_STR("INVALID_FORMAT");
return "INVALID_FORMAT";
if (err == NOISE_ERROR_INVALID_SIGNATURE)
return LOG_STR("INVALID_SIGNATURE");
return LOG_STR("UNKNOWN");
return "INVALID_SIGNATURE";
return to_string(err);
}
/// Initialize the frame helper, returns OK if successful.
@@ -83,11 +75,7 @@ APIError APINoiseFrameHelper::init() {
// init prologue
size_t old_size = prologue_.size();
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
#ifdef USE_ESP8266
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
state_ = State::CLIENT_HELLO;
return APIError::OK;
@@ -95,18 +83,18 @@ APIError APINoiseFrameHelper::init() {
// Helper for handling handshake frame errors
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
send_explicit_handshake_reject_("Bad indicator byte");
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
send_explicit_handshake_reject_("Bad handshake packet len");
}
return aerr;
}
// Helper for handling noise library errors
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
return api_err;
}
return APIError::OK;
@@ -291,11 +279,11 @@ APIError APINoiseFrameHelper::state_action_() {
}
if (frame.empty()) {
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
send_explicit_handshake_reject_("Empty handshake message");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
send_explicit_handshake_reject_("Bad handshake error byte");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
}
@@ -305,10 +293,8 @@ APIError APINoiseFrameHelper::state_action_() {
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) {
// Special handling for MAC failure
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
: LOG_STR("Handshake error"));
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
APIError::HANDSHAKESTATE_READ_FAILED);
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
}
aerr = check_handshake_finished_();
@@ -321,8 +307,8 @@ APIError APINoiseFrameHelper::state_action_() {
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
APIError::HANDSHAKESTATE_WRITE_FAILED);
APIError aerr_write =
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
if (aerr_write != APIError::OK)
return aerr_write;
buffer[0] = 0x00; // success
@@ -345,31 +331,15 @@ APIError APINoiseFrameHelper::state_action_() {
}
return APIError::OK;
}
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
#ifdef USE_STORE_LOG_STR_IN_FLASH
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
std::vector<uint8_t> data;
data.resize(reason_len + 1);
data[0] = 0x01; // failure
// Copy error message from PROGMEM
if (reason_len > 0) {
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
}
#else
// Normal memory access
const char *reason_str = LOG_STR_ARG(reason);
size_t reason_len = strlen(reason_str);
std::vector<uint8_t> data;
data.resize(reason_len + 1);
data.resize(reason.length() + 1);
data[0] = 0x01; // failure
// Copy error message in bulk
if (reason_len > 0) {
std::memcpy(data.data() + 1, reason_str, reason_len);
if (!reason.empty()) {
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
}
#endif
// temporarily remove failed state
auto orig_state = state_;
@@ -398,8 +368,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
APIError decrypt_err =
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
if (decrypt_err != APIError::OK)
return decrypt_err;
@@ -481,8 +450,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
if (aerr != APIError::OK)
return aerr;
@@ -536,27 +504,25 @@ APIError APINoiseFrameHelper::init_handshake_() {
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
const auto &psk = ctx_->get_psk();
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
// set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {};
err = noise_handshakestate_start(handshake_);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
if (aerr != APIError::OK)
return aerr;
return APIError::OK;
@@ -574,8 +540,7 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_BAD_STATE;
}
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
APIError aerr =
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
if (aerr != APIError::OK)
return aerr;

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_();
@@ -32,9 +36,9 @@ class APINoiseFrameHelper final : public APIFrameHelper {
APIError write_frame_(const uint8_t *data, uint16_t len);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const LogString *reason);
void send_explicit_handshake_reject_(const std::string &reason);
APIError handle_handshake_frame_error_(APIError aerr);
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
// Pointers first (4 bytes each)
NoiseHandshakeState *handshake_{nullptr};

View File

@@ -10,10 +10,6 @@
#include <cstring>
#include <cinttypes>
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
namespace esphome::api {
static const char *const TAG = "api.plaintext";
@@ -201,20 +197,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
// We must send at least 3 bytes to be read, so we add
// a message after the indicator byte to ensures its long
// enough and can aid in debugging.
static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
#ifdef USE_ESP8266
static const char MSG_PROGMEM[] PROGMEM = "\x00"
"Bad indicator byte";
char msg[INDICATOR_MSG_SIZE];
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
const char msg[] = "\x00"
"Bad indicator byte";
iov[0].iov_base = (void *) msg;
#else
static const char MSG[] = "\x00"
"Bad indicator byte";
iov[0].iov_base = (void *) MSG;
#endif
iov[0].iov_len = INDICATOR_MSG_SIZE;
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
iov[0].iov_len = 19;
this->write_raw_(iov, 1, 19);
}
return aerr;
}
@@ -248,8 +235,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
for (const auto &packet : packets) {
// Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
uint8_t size_varint_len = api::ProtoSize::varint(packet.payload_size);
uint8_t type_varint_len = api::ProtoSize::varint(packet.message_type);
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header
@@ -284,9 +271,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
buf_start[header_offset] = 0x00; // indicator
// Encode varints directly into buffer
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
ProtoVarInt(packet.message_type)
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
encode_varint_unchecked(buf_start + header_offset + 1, packet.payload_size);
encode_varint_unchecked(buf_start + header_offset + 1 + size_varint_len, packet.message_type);
// Add iovec for this packet (header + payload)
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);

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

@@ -42,8 +42,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->server_info_ref_.size());
size.add_length(1, this->name_ref_.size());
}
#ifdef USE_API_PASSWORD
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->password = value.as_string();
@@ -53,9 +52,8 @@ bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
}
return true;
}
void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
#endif
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
#ifdef USE_AREAS
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->area_id);
@@ -903,16 +901,6 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
return true;
}
#endif
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2:
this->timezone = value.as_string();
break;
default:
return false;
}
return true;
}
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1:
@@ -923,6 +911,8 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
return true;
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name_ref_);
@@ -2163,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

@@ -669,13 +669,8 @@ void HelloResponse::dump_to(std::string &out) const {
dump_field(out, "server_info", this->server_info_ref_);
dump_field(out, "name", this->name_ref_);
}
#ifdef USE_API_PASSWORD
void AuthenticationRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); }
void AuthenticationResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationResponse");
dump_field(out, "invalid_password", this->invalid_password);
}
#endif
void ConnectRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); }
void ConnectResponse::dump_to(std::string &out) const { dump_field(out, "invalid_password", this->invalid_password); }
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
@@ -1115,11 +1110,7 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
}
#endif
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
dump_field(out, "timezone", this->timezone);
}
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
@@ -1144,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);
@@ -1713,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

@@ -24,17 +24,15 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_hello_request(msg);
break;
}
#ifdef USE_API_PASSWORD
case AuthenticationRequest::MESSAGE_TYPE: {
AuthenticationRequest msg;
case ConnectRequest::MESSAGE_TYPE: {
ConnectRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
#endif
this->on_authentication_request(msg);
this->on_connect_request(msg);
break;
}
#endif
case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg;
// Empty message: no decode needed
@@ -162,6 +160,15 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break;
}
#endif
case GetTimeRequest::MESSAGE_TYPE: {
GetTimeRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif
this->on_get_time_request(msg);
break;
}
case GetTimeResponse::MESSAGE_TYPE: {
GetTimeResponse msg;
msg.decode(msg_data, msg_size);
@@ -599,13 +606,11 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
this->on_fatal_error();
}
}
#ifdef USE_API_PASSWORD
void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
if (!this->send_authenticate_response(msg)) {
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
if (!this->send_connect_response(msg)) {
this->on_fatal_error();
}
}
#endif
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
if (!this->send_disconnect_response(msg)) {
this->on_fatal_error();
@@ -651,6 +656,11 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
}
}
#endif
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
this->on_fatal_error();
}
}
#ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
if (this->check_authenticated_()) {

View File

@@ -26,9 +26,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_hello_request(const HelloRequest &value){};
#ifdef USE_API_PASSWORD
virtual void on_authentication_request(const AuthenticationRequest &value){};
#endif
virtual void on_connect_request(const ConnectRequest &value){};
virtual void on_disconnect_request(const DisconnectRequest &value){};
virtual void on_disconnect_response(const DisconnectResponse &value){};
@@ -73,7 +71,7 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
#endif
virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
@@ -215,9 +213,7 @@ class APIServerConnectionBase : public ProtoService {
class APIServerConnection : public APIServerConnectionBase {
public:
virtual bool send_hello_response(const HelloRequest &msg) = 0;
#ifdef USE_API_PASSWORD
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
#endif
virtual bool send_connect_response(const ConnectRequest &msg) = 0;
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
@@ -230,6 +226,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
@@ -338,9 +335,7 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
#ifdef USE_API_PASSWORD
void on_authentication_request(const AuthenticationRequest &msg) override;
#endif
void on_connect_request(const ConnectRequest &msg) override;
void on_disconnect_request(const DisconnectRequest &msg) override;
void on_ping_request(const PingRequest &msg) override;
void on_device_info_request(const DeviceInfoRequest &msg) override;
@@ -353,6 +348,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif
void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif

View File

@@ -62,11 +62,9 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
nanoseconds = time_.microsecond // 1000
timestamp = (
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
)
for parsed_msg in parse_log_message(text, timestamp):
for parsed_msg in parse_log_message(
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
):
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
stop = await async_run(cli, on_log, name=name)

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

@@ -124,34 +124,6 @@ class ProtoVarInt {
// with ZigZag encoding
return decode_zigzag64(this->value_);
}
/**
* Encode the varint value to a pre-allocated buffer without bounds checking.
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param len The size of the buffer in bytes
*
* @note The caller is responsible for ensuring the buffer is large enough
* to hold the encoded value. Use ProtoSize::varint() to calculate
* the exact size needed before calling this method.
* @note No bounds checking is performed for performance reasons.
*/
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
uint64_t val = this->value_;
if (val <= 0x7F) {
buffer[0] = val;
return;
}
size_t i = 0;
while (val && i < len) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
buffer[i++] = temp | 0x80;
} else {
buffer[i++] = temp;
}
}
}
void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_;
if (val <= 0x7F) {
@@ -330,6 +302,28 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_;
};
/**
* @brief Encode a uint16_t value as a varint directly to a buffer without bounds checking
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param value The uint16_t value to encode (0-65535)
*
* @note The caller is responsible for ensuring the buffer is large enough (max 3 bytes for uint16_t)
* @note No bounds checking is performed for performance reasons
*/
inline void encode_varint_unchecked(uint8_t *buffer, uint16_t value) {
if (value < 128) {
buffer[0] = value;
} else if (value < 16384) {
buffer[0] = (value & 0x7F) | 0x80;
buffer[1] = value >> 7;
} else {
buffer[0] = (value & 0x7F) | 0x80;
buffer[1] = ((value >> 7) & 0x7F) | 0x80;
buffer[2] = value >> 14;
}
}
// Forward declaration
class ProtoSize;
@@ -386,6 +380,33 @@ class ProtoSize {
uint32_t get_size() const { return total_size_; }
/**
* @brief Calculates the size in bytes needed to encode a uint8_t value as a varint
*
* @param value The uint8_t value to calculate size for
* @return The number of bytes needed to encode the value (1 or 2)
*/
static constexpr uint8_t varint(uint8_t value) {
// For uint8_t (0-255), we need at most 2 bytes
return (value < 128) ? 1 : 2;
}
/**
* @brief Calculates the size in bytes needed to encode a uint16_t value as a varint
*
* @param value The uint16_t value to calculate size for
* @return The number of bytes needed to encode the value (1-3)
*/
static constexpr uint8_t varint(uint16_t value) {
// For uint16_t (0-65535), we need at most 3 bytes
if (value < 128)
return 1; // 7 bits
else if (value < 16384)
return 2; // 14 bits
else
return 3; // 15-16 bits
}
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
@@ -395,11 +416,9 @@ class ProtoSize {
static constexpr uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
if (value < 128) {
return 1; // 7 bits, common case for small values
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
} else if (value < 16384) {
return 2; // 14 bits
} else if (value < 2097152) {
return 3; // 21 bits
@@ -773,7 +792,7 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
// Write the length varint directly
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
encode_varint_unchecked(this->buffer_->data() + begin, static_cast<uint16_t>(msg_length_bytes));
// Now encode the message content - it will append to the buffer
value.encode(*this);

View File

@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_CLEAR,
CONF_GAIN,
CONF_ID,
DEVICE_CLASS_ILLUMINANCE,
@@ -30,6 +29,7 @@ CONF_F5 = "f5"
CONF_F6 = "f6"
CONF_F7 = "f7"
CONF_F8 = "f8"
CONF_CLEAR = "clear"
CONF_NIR = "nir"
UNIT_COUNTS = "#"

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

@@ -16,7 +16,6 @@ from esphome.const import (
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_LIGHTBULB,
@@ -79,7 +78,6 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

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

@@ -17,12 +17,10 @@ from esphome.const import (
CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
@@ -102,13 +100,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
device_class=DEVICE_CLASS_REACTIVE_POWER,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_APPARENT_POWER,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(

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

@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
#define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \
this->status_set_warning(LOG_STR("Failed to communicate")); \
this->status_set_warning("Failed to communicate"); \
return; \
}
@@ -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

@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
" ble_client.app_id: %d\n"
" ble_client.conn_id: %d",
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
LOG_UPDATE_INTERVAL(this);
LOG_UPDATE_INTERVAL(this)
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
for (auto *child : this->children_) {
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());

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

@@ -1,6 +1 @@
import esphome.codegen as cg
CODEOWNERS = ["@tobias-", "@dan-s-github"]
CONF_BL0940_ID = "bl0940_id"
bl0940_ns = cg.esphome_ns.namespace("bl0940")
CODEOWNERS = ["@tobias-"]

View File

@@ -7,26 +7,28 @@ namespace bl0940 {
static const char *const TAG = "bl0940";
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
static const uint8_t BL0940_FULL_PACKET = 0xAA;
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0940_REG_MODE = 0x18;
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
static const uint8_t BL0940_INIT[5][5] = {
const uint8_t BL0940_INIT[5][6] = {
// Reset to default
{BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
void BL0940::loop() {
DataPacket buffer;
@@ -34,8 +36,8 @@ void BL0940::loop() {
return;
}
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (this->validate_checksum_(&buffer)) {
this->received_package_(&buffer);
if (validate_checksum(&buffer)) {
received_package_(&buffer);
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
@@ -44,151 +46,35 @@ void BL0940::loop() {
}
}
bool BL0940::validate_checksum_(DataPacket *data) {
uint8_t checksum = this->read_command_;
bool BL0940::validate_checksum(const DataPacket *data) {
uint8_t checksum = BL0940_READ_COMMAND;
// Whole package but checksum
uint8_t *raw = (uint8_t *) data;
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
checksum += raw[i];
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
checksum += data->raw[i];
}
checksum ^= 0xFF;
if (checksum != data->checksum) {
ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
}
return checksum == data->checksum;
}
void BL0940::update() {
this->flush();
this->write_byte(this->read_command_);
this->write_byte(BL0940_READ_COMMAND);
this->write_byte(BL0940_FULL_PACKET);
}
void BL0940::setup() {
#ifdef USE_NUMBER
// add calibration callbacks
if (this->voltage_calibration_number_ != nullptr) {
this->voltage_calibration_number_->add_on_state_callback(
[this](float state) { this->voltage_calibration_callback_(state); });
if (this->voltage_calibration_number_->has_state()) {
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
}
}
if (this->current_calibration_number_ != nullptr) {
this->current_calibration_number_->add_on_state_callback(
[this](float state) { this->current_calibration_callback_(state); });
if (this->current_calibration_number_->has_state()) {
this->current_calibration_callback_(this->current_calibration_number_->state);
}
}
if (this->power_calibration_number_ != nullptr) {
this->power_calibration_number_->add_on_state_callback(
[this](float state) { this->power_calibration_callback_(state); });
if (this->power_calibration_number_->has_state()) {
this->power_calibration_callback_(this->power_calibration_number_->state);
}
}
if (this->energy_calibration_number_ != nullptr) {
this->energy_calibration_number_->add_on_state_callback(
[this](float state) { this->energy_calibration_callback_(state); });
if (this->energy_calibration_number_->has_state()) {
this->energy_calibration_callback_(this->energy_calibration_number_->state);
}
}
#endif
// calculate calibrated reference values
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
for (auto *i : BL0940_INIT) {
this->write_byte(this->write_command_), this->write_array(i, 5);
this->write_array(i, 6);
delay(1);
}
this->flush();
}
float BL0940::calculate_power_reference_() {
// calculate power reference based on voltage and current reference
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
}
float BL0940::calculate_energy_reference_() {
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
// or: power_reference_ * 3600000 / (1638.4 * 256)
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
}
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
void BL0940::reset_calibration() {
#ifdef USE_NUMBER
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
this->current_calibration_number_->make_call().set_value(0).perform();
}
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
this->voltage_calibration_number_->make_call().set_value(0).perform();
}
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
this->power_calibration_number_->make_call().set_value(0).perform();
}
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
this->energy_calibration_number_->make_call().set_value(0).perform();
}
#endif
ESP_LOGD(TAG, "external calibration values restored to initial state");
}
void BL0940::current_calibration_callback_(float state) {
this->current_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
this->recalibrate_();
}
void BL0940::voltage_calibration_callback_(float state) {
this->voltage_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
this->recalibrate_();
}
void BL0940::power_calibration_callback_(float state) {
this->power_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
this->recalibrate_();
}
void BL0940::energy_calibration_callback_(float state) {
this->energy_cal_ = this->calculate_calibration_value_(state);
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
this->recalibrate_();
}
void BL0940::recalibrate_() {
ESP_LOGV(TAG, "Recalibrating reference values");
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
this->power_reference_ = this->calculate_power_reference_();
}
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
this->energy_reference_ = this->calculate_energy_reference_();
}
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
ESP_LOGD(TAG,
"Recalibrated reference values:\n"
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
this->energy_reference_cal_);
}
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
auto tb = (float) temperature;
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
auto tb = (float) (temperature.h << 8 | temperature.l);
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
@@ -201,40 +87,33 @@ float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) cons
return converted_temp;
}
void BL0940::received_package_(DataPacket *data) {
void BL0940::received_package_(const DataPacket *data) const {
// Bad header
if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
// cf_cnt is only 24 bits, so track overflows
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
if (cf_cnt < this->prev_cf_cnt_) {
cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
float watt = (float) to_int32_t(data->watt) / power_reference_;
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
float total_energy_consumption = (float) cf_cnt / energy_reference_;
float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
float watt = (int24_t) data->watt / this->power_reference_cal_;
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(v_rms);
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(v_rms);
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(i_rms);
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(i_rms);
}
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(watt);
if (power_sensor_ != nullptr) {
power_sensor_->publish_state(watt);
}
if (this->energy_sensor_ != nullptr) {
this->energy_sensor_->publish_state(total_energy_consumption);
if (energy_sensor_ != nullptr) {
energy_sensor_->publish_state(total_energy_consumption);
}
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
@@ -242,27 +121,7 @@ void BL0940::received_package_(DataPacket *data) {
}
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG,
"BL0940:\n"
" LEGACY MODE: %s\n"
" READ CMD: 0x%02X\n"
" WRITE CMD: 0x%02X\n"
" ------------------\n"
" Current reference: %f\n"
" Energy reference: %f\n"
" Power reference: %f\n"
" Voltage reference: %f\n",
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG,
"BL0940:\n"
" Current calibration: %f\n"
" Energy calibration: %f\n"
" Power calibration: %f\n"
" Voltage calibration: %f\n",
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
#endif
ESP_LOGCONFIG(TAG, "BL0940:");
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
@@ -271,5 +130,9 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
}
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
} // namespace bl0940
} // namespace esphome

View File

@@ -1,48 +1,66 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace bl0940 {
static const float BL0940_PREF = 1430;
static const float BL0940_UREF = 33000;
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
// Measured to 297J per click according to power consumption of 5 minutes
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
static const float BL0940_EREF = 3.6e6 / 297;
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
uint8_t h;
} __attribute__((packed));
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t h;
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
int8_t h;
} __attribute__((packed));
// Caveat: All these values are big endian (low - middle - high)
struct DataPacket {
uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
uint24_le_t i_fast_rms; // Fast RMS current
uint24_le_t i_rms; // RMS current
uint24_t RESERVED0; // Reserved
uint24_le_t v_rms; // RMS voltage
uint24_t RESERVED1; // Reserved
int24_le_t watt; // Active power (can be negative for bidirectional measurement)
uint24_t RESERVED2; // Reserved
uint24_le_t cf_cnt; // Energy pulse count
uint24_t RESERVED3; // Reserved
uint16_le_t tps1; // Internal temperature sensor 1
uint8_t RESERVED4; // Reserved (should be 0x00)
uint16_le_t tps2; // Internal temperature sensor 2
uint8_t RESERVED5; // Reserved (should be 0x00)
uint8_t checksum; // Packet checksum
union DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t raw[35];
struct {
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
ube24_t i_fast_rms; // 0x00
ube24_t i_rms; // 0x04
ube24_t RESERVED0; // reserved
ube24_t v_rms; // 0x06
ube24_t RESERVED1; // reserved
sbe24_t watt; // 0x08
ube24_t RESERVED2; // reserved
ube24_t cf_cnt; // 0x0A
ube24_t RESERVED3; // reserved
ube16_t tps1; // 0x0c
uint8_t RESERVED4; // value of 0x00
ube16_t tps2; // 0x0c
uint8_t RESERVED5; // value of 0x00
uint8_t checksum; // checksum
};
} __attribute__((packed));
class BL0940 : public PollingComponent, public uart::UARTDevice {
public:
// Sensor setters
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
// Temperature sensor setters
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
internal_temperature_sensor_ = internal_temperature_sensor;
}
@@ -50,105 +68,42 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
external_temperature_sensor_ = external_temperature_sensor;
}
// Configuration setters
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
// Reference value setters (used for calibration and conversion)
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
#ifdef USE_NUMBER
// Calibration number setters (for Home Assistant number entities)
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
#endif
#ifdef USE_BUTTON
// Resets all calibration values to defaults (can be triggered by a button)
void reset_calibration();
#endif
// Core component methods
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
// --- Sensor pointers ---
sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
sensor::Sensor *current_sensor_{nullptr}; // Current sensor
sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
// NB This may be negative as the circuits is seemingly able to measure
// power in both directions
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
sensor::Sensor *internal_temperature_sensor_{nullptr};
sensor::Sensor *external_temperature_sensor_{nullptr};
#ifdef USE_NUMBER
// --- Calibration number entities (for dynamic calibration via HA UI) ---
number::Number *voltage_calibration_number_{nullptr};
number::Number *current_calibration_number_{nullptr};
number::Number *power_calibration_number_{nullptr};
number::Number *energy_calibration_number_{nullptr};
#endif
// Max difference between two measurements of the temperature. Used to avoid noise.
float max_temperature_diff_{0};
// Divide by this to turn into Watt
float power_reference_ = BL0940_PREF;
// Divide by this to turn into Volt
float voltage_reference_ = BL0940_UREF;
// Divide by this to turn into Ampere
float current_reference_ = BL0940_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0940_EREF;
// --- Internal state ---
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
// --- Reference values for conversion ---
float power_reference_; // Divider for raw power to get Watts
float power_reference_cal_; // Calibrated power reference
float voltage_reference_; // Divider for raw voltage to get Volts
float voltage_reference_cal_; // Calibrated voltage reference
float current_reference_; // Divider for raw current to get Amperes
float current_reference_cal_; // Calibrated current reference
float energy_reference_; // Divider for raw energy to get kWh
float energy_reference_cal_; // Calibrated energy reference
static uint32_t to_uint32_t(ube24_t input);
// --- Home Assistant calibration values (multipliers, default 1) ---
float current_cal_{1};
float voltage_cal_{1};
float power_cal_{1};
float energy_cal_{1};
static int32_t to_int32_t(sbe24_t input);
// --- Protocol commands ---
uint8_t read_command_;
uint8_t write_command_;
static bool validate_checksum(const DataPacket *data);
// --- Mode flags ---
bool legacy_mode_enabled_ = true;
// --- Methods ---
// Converts packed temperature value to float and updates the sensor
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
// Validates the checksum of a received data packet
bool validate_checksum_(DataPacket *data);
// Handles a received data packet
void received_package_(DataPacket *data);
// Calculates reference values for calibration and conversion
float calculate_energy_reference_();
float calculate_power_reference_();
float calculate_calibration_value_(float state);
// Calibration update callbacks (used with number entities)
void current_calibration_callback_(float state);
void voltage_calibration_callback_(float state);
void power_calibration_callback_(float state);
void energy_calibration_callback_(float state);
void reset_calibration_callback_();
// Recalculates all reference values after calibration changes
void recalibrate_();
void received_package_(const DataPacket *data) const;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -1,27 +0,0 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
CalibrationResetButton = bl0940_ns.class_(
"CalibrationResetButton", button.Button, cg.Component
)
CONFIG_SCHEMA = cv.All(
button.button_schema(
CalibrationResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
)
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await button.new_button(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_BL0940_ID])

View File

@@ -1,20 +0,0 @@
#include "calibration_reset_button.h"
#include "../bl0940.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.button.calibration_reset";
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
void CalibrationResetButton::press_action() {
ESP_LOGI(TAG, "Resetting calibration defaults...");
this->parent_->reset_calibration();
}
} // namespace bl0940
} // namespace esphome

View File

@@ -1,19 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace bl0940 {
class BL0940; // Forward declaration of BL0940 class
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
public:
void dump_config() override;
void press_action() override;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -1,94 +0,0 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_MODE,
CONF_RESTORE_VALUE,
CONF_STEP,
ENTITY_CATEGORY_CONFIG,
UNIT_PERCENT,
)
from .. import CONF_BL0940_ID, bl0940_ns
from ..sensor import BL0940
# Define calibration types
CONF_CURRENT_CALIBRATION = "current_calibration"
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
CONF_POWER_CALIBRATION = "power_calibration"
CONF_ENERGY_CALIBRATION = "energy_calibration"
BL0940Number = bl0940_ns.class_("BL0940Number")
CalibrationNumber = bl0940_ns.class_(
"CalibrationNumber", number.Number, cg.PollingComponent
)
def validate_min_max(config):
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
raise cv.Invalid("max_value must be greater than min_value")
return config
CALIBRATION_SCHEMA = cv.All(
number.number_schema(
CalibrationNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
unit_of_measurement=UNIT_PERCENT,
)
.extend(
{
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
cv.float_, cv.Range(max=50)
),
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
cv.float_, cv.Range(min=-50)
),
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA),
validate_min_max,
)
# Configuration schema for BL0940 numbers
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0940Number),
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
}
)
async def to_code(config):
# Get the BL0940 component instance
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
# Process all calibration types
for cal_type, setter_method in [
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
]:
if conf := config.get(cal_type):
var = await number.new_number(
conf,
min_value=conf.get(CONF_MIN_VALUE),
max_value=conf.get(CONF_MAX_VALUE),
step=conf.get(CONF_STEP),
)
await cg.register_component(var, conf)
if restore_value := config.get(CONF_RESTORE_VALUE):
cg.add(var.set_restore_value(restore_value))
cg.add(getattr(bl0940, setter_method)(var))

View File

@@ -1,29 +0,0 @@
#include "calibration_number.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() {
float value = 0.0f;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
if (!this->pref_.load(&value)) {
value = 0.0f;
}
}
this->publish_state(value);
}
void CalibrationNumber::control(float value) {
this->publish_state(value);
if (this->restore_value_)
this->pref_.save(&value);
}
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
} // namespace bl0940
} // namespace esphome

View File

@@ -1,26 +0,0 @@
#pragma once
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace bl0940 {
class CalibrationNumber : public number::Number, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
void control(float value) override;
bool restore_value_{true};
ESPPreferenceObject pref_;
};
} // namespace bl0940
} // namespace esphome

View File

@@ -8,7 +8,6 @@ from esphome.const import (
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_POWER,
CONF_REFERENCE_VOLTAGE,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
@@ -24,133 +23,12 @@ from esphome.const import (
UNIT_WATT,
)
from . import bl0940_ns
DEPENDENCIES = ["uart"]
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
CONF_LEGACY_MODE = "legacy_mode"
CONF_READ_COMMAND = "read_command"
CONF_WRITE_COMMAND = "write_command"
CONF_RESISTOR_SHUNT = "resistor_shunt"
CONF_RESISTOR_ONE = "resistor_one"
CONF_RESISTOR_TWO = "resistor_two"
CONF_CURRENT_REFERENCE = "current_reference"
CONF_ENERGY_REFERENCE = "energy_reference"
CONF_POWER_REFERENCE = "power_reference"
CONF_VOLTAGE_REFERENCE = "voltage_reference"
DEFAULT_BL0940_READ_COMMAND = 0x58
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
# Values according to BL0940 application note:
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
# ----------------------------------------------------
# values from initial implementation
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
DEFAULT_BL0940_LEGACY_UREF = 33000
DEFAULT_BL0940_LEGACY_IREF = 275000
DEFAULT_BL0940_LEGACY_PREF = 1430
# Measured to 297J per click according to power consumption of 5 minutes
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
# ----------------------------------------------------
# methods to calculate voltage and current reference values
def calculate_voltage_reference(vref, r_one, r_two):
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
def calculate_current_reference(vref, r_shunt):
# formula: 324004 * RL / Vref
return 324004 * r_shunt / vref
def calculate_power_reference(voltage_reference, current_reference):
# calculate power reference based on voltage and current reference
return voltage_reference * current_reference * 4046 / 324004 / 79931
def calculate_energy_reference(power_reference):
# formula: power_reference * 3600000 / (1638.4 * 256)
return power_reference * 3600000 / (1638.4 * 256)
def validate_legacy_mode(config):
# Only allow schematic calibration options if legacy_mode is False
if config.get(CONF_LEGACY_MODE, True):
forbidden = [
CONF_REFERENCE_VOLTAGE,
CONF_RESISTOR_SHUNT,
CONF_RESISTOR_ONE,
CONF_RESISTOR_TWO,
]
for key in forbidden:
if key in config:
raise cv.Invalid(
f"Option '{key}' is only allowed when legacy_mode: false"
)
return config
def set_command_defaults(config):
# Set defaults for read_command and write_command based on legacy_mode
legacy = config.get(CONF_LEGACY_MODE, True)
if legacy:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
else:
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
return config
def set_reference_values(config):
# Set default reference values based on legacy_mode
if config.get(CONF_LEGACY_MODE, True):
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
else:
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
config.setdefault(
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
)
config.setdefault(
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
)
config.setdefault(
CONF_POWER_REFERENCE,
calculate_power_reference(
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
),
)
config.setdefault(
CONF_ENERGY_REFERENCE,
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
)
return config
CONFIG_SCHEMA = (
cv.Schema(
{
@@ -191,24 +69,10 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
.add_extra(validate_legacy_mode)
.add_extra(set_command_defaults)
.add_extra(set_reference_values)
)
@@ -235,16 +99,3 @@ async def to_code(config):
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(external_temperature_config)
cg.add(var.set_external_temperature_sensor(sens))
# enable legacy mode
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
# Set bl0940 commands after validator has determined which defaults to use if not set
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
# Set reference values after validator has set the values either from defaults or calculated
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))

View File

@@ -149,7 +149,7 @@ void BL0942::setup() {
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning(LOG_STR("BL0942 setup failed!"));
this->status_set_warning("BL0942 setup failed!");
this->flush();
}

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

@@ -6,6 +6,8 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_ID
from esphome.core import CORE
from esphome.log import AnsiFore, color
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"]
@@ -46,6 +48,26 @@ def validate_connections(config):
config
)
# Warn about connection slot waste when using Arduino framework
if CORE.using_arduino and connection_slots:
_LOGGER.warning(
"Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
"If BLE connections fail, they can waste connection slots for 10 seconds because\n"
"Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
"ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
"\n"
"To switch to ESP-IDF, add this to your YAML:\n"
" esp32:\n"
" framework:\n"
" type: esp-idf\n"
"\n"
"For detailed migration instructions, see:\n"
"%s",
color(
AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
),
)
return {
**config,
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
@@ -58,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
),
@@ -67,11 +89,11 @@ CONFIG_SCHEMA = cv.All(
default=DEFAULT_CONNECTION_SLOTS,
): cv.All(
cv.positive_int,
cv.Range(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS),
cv.Range(min=1, max=esp32_ble_tracker.max_connections()),
),
cv.Optional(CONF_CONNECTIONS): cv.All(
cv.ensure_list(CONNECTION_SCHEMA),
cv.Length(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS),
cv.Length(min=1, max=esp32_ble_tracker.max_connections()),
),
}
)

View File

@@ -375,19 +375,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();
@@ -130,9 +130,7 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
std::string get_bluetooth_mac_address_pretty() {
const uint8_t *mac = esp_bt_dev_get_address();
char buf[18];
format_mac_addr_upper(mac, buf);
return std::string(buf);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
protected:
@@ -163,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

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

@@ -63,12 +63,12 @@ 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);
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);
return;
@@ -80,7 +80,7 @@ void BMP280Component::setup() {
}
// Send a soft reset.
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed("Reset failed");
return;
}
@@ -89,7 +89,7 @@ void BMP280Component::setup() {
uint8_t retry = 5;
do {
delay(2);
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
return;
}
@@ -115,14 +115,14 @@ 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)) {
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
return;
}
@@ -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;
}
@@ -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

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