mirror of
https://github.com/esphome/esphome.git
synced 2026-08-23 22:56:19 +00:00
Compare commits
115
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ab0805a0 | ||
|
|
017040ec8e | ||
|
|
52fe461e9c | ||
|
|
a7dad14449 | ||
|
|
1493a09521 | ||
|
|
2a59c0ad9e | ||
|
|
70421fb14b | ||
|
|
bacc223360 | ||
|
|
94be1da893 | ||
|
|
cb66dc01ab | ||
|
|
ebaf32c82a | ||
|
|
f918c299b1 | ||
|
|
328a2018d2 | ||
|
|
a4a9feac16 | ||
|
|
6f27c7f3fe | ||
|
|
300ab1be35 | ||
|
|
7aed5fb94b | ||
|
|
2132cae1c9 | ||
|
|
735f8d607d | ||
|
|
cc6392785f | ||
|
|
7a2d13da90 | ||
|
|
e2df9fb554 | ||
|
|
4507d05822 | ||
|
|
3caae3031a | ||
|
|
a624659856 | ||
|
|
6c8d0050ab | ||
|
|
09da2766ed | ||
|
|
72bd45538e | ||
|
|
5b37049dff | ||
|
|
c0e78a5574 | ||
|
|
f53394e46f | ||
|
|
2218edf20c | ||
|
|
dcdd044234 | ||
|
|
bc8510c6d8 | ||
|
|
5b4ae22f58 | ||
|
|
14e71e190c | ||
|
|
2333e6eef5 | ||
|
|
d748c04b28 | ||
|
|
8d1a0446a2 | ||
|
|
0887e01828 | ||
|
|
f88621f2e1 | ||
|
|
ec3f7ec16e | ||
|
|
880cb1db43 | ||
|
|
f336c45177 | ||
|
|
b12d392e02 | ||
|
|
49a1d2bb1b | ||
|
|
e327a7f52f | ||
|
|
90e838d327 | ||
|
|
f91aa7aa13 | ||
|
|
be3b27f378 | ||
|
|
3439a08b68 | ||
|
|
dedca344f9 | ||
|
|
c4975e1870 | ||
|
|
5a3c2f4d11 | ||
|
|
e700b01406 | ||
|
|
d53d4c5b58 | ||
|
|
3c34fc950e | ||
|
|
b0489e21b2 | ||
|
|
a3ab7961e0 | ||
|
|
54987d7d23 | ||
|
|
053ce39fc6 | ||
|
|
b295b8d5a2 | ||
|
|
4d3d06959b | ||
|
|
a5583dcba6 | ||
|
|
e2b62bcd00 | ||
|
|
4275343231 | ||
|
|
4ebe49b141 | ||
|
|
d3adb6fb81 | ||
|
|
2753ab1f45 | ||
|
|
b6b5b61640 | ||
|
|
db9a09d05a | ||
|
|
ca77cc585c | ||
|
|
f1e4726f4e | ||
|
|
49bbceb1da | ||
|
|
65d6c028ce | ||
|
|
5e3e2f82c9 | ||
|
|
d78cb09b17 | ||
|
|
bcfb438a81 | ||
|
|
a8dfd00cc6 | ||
|
|
4a82b10783 | ||
|
|
07460ebee4 | ||
|
|
cdc42e5e55 | ||
|
|
b3e03868b3 | ||
|
|
0ff11674ef | ||
|
|
434cffb745 | ||
|
|
2b3027a7fd | ||
|
|
e27a14ec70 | ||
|
|
91c42381f6 | ||
|
|
a39607476a | ||
|
|
b098571a6f | ||
|
|
614fd88829 | ||
|
|
c0636e2bf7 | ||
|
|
65353006c8 | ||
|
|
35a99f478e | ||
|
|
b6a4dd237e | ||
|
|
0ef85783dc | ||
|
|
5020179210 | ||
|
|
54529412dc | ||
|
|
e6525b5d93 | ||
|
|
9f62cf9243 | ||
|
|
478bca026c | ||
|
|
e1719cd85d | ||
|
|
83aaed71e1 | ||
|
|
88ca0d44e0 | ||
|
|
7a1e0bbbec | ||
|
|
b2226b91ff | ||
|
|
ba84f2ec55 | ||
|
|
4b19de0c1a | ||
|
|
4292e7988e | ||
|
|
9f21fd0b55 | ||
|
|
9327d011fc | ||
|
|
435dde67d0 | ||
|
|
9c92ab63fb | ||
|
|
acc8381cbb | ||
|
|
0a8a7e22d2 |
@@ -465,7 +465,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@9f3a37ece7abc84992501a7fcd54d1704f3458fa # v4.18.4
|
||||
uses: CodSpeedHQ/action@f99becdce5e5d51fd556489ebef684f4ecfd6286 # v4.18.5
|
||||
with:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -504,6 +504,15 @@ jobs:
|
||||
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
|
||||
cache_sdk_nrf: true
|
||||
ignore_errors: false
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for RP2
|
||||
options: --environment rp2-tidy --grep USE_RP2
|
||||
pio_cache_key: tidyrp2
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for LibreTiny
|
||||
environments: bk72xx-tidy ln882h-tidy rtl87xxb-tidy rtl87xxc-tidy
|
||||
options: --grep USE_LIBRETINY --grep USE_BK72XX --grep USE_RTL87XX --grep USE_LN882X
|
||||
pio_cache_key: tidylibretiny
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@@ -567,10 +576,21 @@ jobs:
|
||||
. venv/bin/activate
|
||||
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
|
||||
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||
changed=""
|
||||
else
|
||||
echo "Running clang-tidy on changed files only"
|
||||
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||
changed="--changed"
|
||||
fi
|
||||
if [ -n "${{ matrix.environments }}" ]; then
|
||||
rc=0
|
||||
for env in ${{ matrix.environments }}; do
|
||||
echo "::group::clang-tidy $env"
|
||||
script/clang-tidy --all-headers --fix $changed --environment "$env" ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} || rc=1
|
||||
echo "::endgroup::"
|
||||
done
|
||||
exit $rc
|
||||
else
|
||||
script/clang-tidy --all-headers --fix $changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||
fi
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
@@ -744,7 +764,8 @@ jobs:
|
||||
include:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 S3
|
||||
options: --environment esp32s3-idf-tidy --grep USE_ESP32_VARIANT_ESP32S3
|
||||
# yamllint disable-line rule:line-length
|
||||
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 P4
|
||||
# P4 has no native Wi-Fi/BLE; those run over the hosted co-processor,
|
||||
@@ -754,7 +775,7 @@ jobs:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 C6
|
||||
# yamllint disable-line rule:line-length
|
||||
options: --environment esp32c6-idf-tidy --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
|
||||
options: --environment esp32c6-idf-tidy --grep SOC_LP_I2C_SUPPORTED --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -14,4 +14,4 @@ jobs:
|
||||
permissions:
|
||||
issues: write # issues.lock on closed issues
|
||||
pull-requests: write # issues.lock on closed pull requests
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Stale
|
||||
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
with:
|
||||
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||
remove-stale-when-updated: true
|
||||
|
||||
@@ -145,6 +145,7 @@ esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/ds2484/* @mrk-its
|
||||
esphome/components/ds248x/* @tomwellnitz
|
||||
esphome/components/dsmr/* @glmnet @PolarGoose
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
|
||||
@@ -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 = 2026.7.0
|
||||
PROJECT_NUMBER = 2026.8.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -6,3 +6,4 @@ recursive-include esphome *.cpp *.h *.tcc *.c
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome *.jinja
|
||||
recursive-include esphome LICENSE.txt
|
||||
recursive-include esphome requirements.txt
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.6.1
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.6.4
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -20,6 +20,7 @@ from . import (
|
||||
RAM_SECTIONS,
|
||||
MemoryAnalyzer,
|
||||
)
|
||||
from .toolchain import find_elf_path, find_idedata_path, idedata_candidates
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ComponentMemory
|
||||
@@ -759,45 +760,25 @@ def main():
|
||||
print(f"Error: {build_path} is not a directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Find firmware.elf
|
||||
elf_file = None
|
||||
for elf_candidate in [
|
||||
build_path / "firmware.elf",
|
||||
build_path / ".pioenvs" / build_path.name / "firmware.elf",
|
||||
]:
|
||||
if elf_candidate.exists():
|
||||
elf_file = str(elf_candidate)
|
||||
break
|
||||
|
||||
if not elf_file:
|
||||
print(f"Error: firmware.elf not found in {build_dir}", file=sys.stderr)
|
||||
elf_path = find_elf_path(build_path)
|
||||
if not elf_path:
|
||||
print(f"Error: no firmware ELF found in {build_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Find idedata.json - check current directory first, then home
|
||||
device_name = build_path.name
|
||||
idedata_candidates = [
|
||||
Path.cwd() / ".esphome" / "idedata" / f"{device_name}.json",
|
||||
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
|
||||
]
|
||||
elf_file = str(elf_path)
|
||||
|
||||
idedata = None
|
||||
for idedata_path in idedata_candidates:
|
||||
if not idedata_path.exists():
|
||||
continue
|
||||
if idedata_path := find_idedata_path(build_path):
|
||||
try:
|
||||
with idedata_path.open(encoding="utf-8") as f:
|
||||
raw_data = json.load(f)
|
||||
idedata = IDEData(raw_data)
|
||||
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
|
||||
break
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr)
|
||||
|
||||
if not idedata:
|
||||
print(
|
||||
f"Warning: idedata not found (searched {idedata_candidates[0]} and {idedata_candidates[1]})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
searched = "\n ".join(str(p) for p in idedata_candidates(build_path))
|
||||
print(f"Warning: idedata not found, searched:\n {searched}", file=sys.stderr)
|
||||
|
||||
analyzer = MemoryAnalyzerCLI(elf_file, idedata=idedata)
|
||||
analyzer.analyze()
|
||||
|
||||
@@ -23,6 +23,78 @@ TOOLCHAIN_PREFIXES = [
|
||||
]
|
||||
|
||||
|
||||
def find_elf_path(build_path: Path) -> Path | None:
|
||||
"""Locate the firmware ELF inside an ESPHome build directory.
|
||||
|
||||
The layout depends on the toolchain that produced the build, so try each
|
||||
known one in turn.
|
||||
|
||||
Args:
|
||||
build_path: Path to an ESPHome build directory
|
||||
|
||||
Returns:
|
||||
Path to the ELF file, or None if no known layout matches
|
||||
"""
|
||||
name = build_path.name
|
||||
for candidate in (
|
||||
# Native ESP-IDF: idf.py writes build/<name>.elf, which ESPHome copies
|
||||
# to build/firmware.elf (see espidf.toolchain.create_elf_copy)
|
||||
build_path / "build" / "firmware.elf",
|
||||
# PlatformIO
|
||||
build_path / "firmware.elf",
|
||||
build_path / ".pioenvs" / name / "firmware.elf",
|
||||
# LibreTiny uses raw_firmware.elf
|
||||
build_path / "raw_firmware.elf",
|
||||
build_path / ".pioenvs" / name / "raw_firmware.elf",
|
||||
# Zephyr (nRF52); the SDK nests the artifacts one level deeper from 2.9.2
|
||||
build_path / ".pioenvs" / name / "zephyr" / "zephyr" / "zephyr.elf",
|
||||
build_path / ".pioenvs" / name / "zephyr" / "zephyr.elf",
|
||||
):
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def idedata_candidates(build_path: Path) -> list[Path]:
|
||||
"""Return the idedata locations searched for a build directory, in order.
|
||||
|
||||
Exposed so a caller reporting "not found" can name the paths it tried
|
||||
without keeping its own copy of the list.
|
||||
|
||||
Args:
|
||||
build_path: Path to an ESPHome build directory
|
||||
|
||||
Returns:
|
||||
The candidate idedata JSON paths, most specific first
|
||||
"""
|
||||
name = build_path.name
|
||||
return [
|
||||
# In .pioenvs for test builds
|
||||
build_path / ".pioenvs" / name / "idedata.json",
|
||||
# Both toolchains cache it in the data dir, which holds this build dir:
|
||||
# <data_dir>/idedata/<name>.json next to <data_dir>/build/<name>
|
||||
build_path.parent.parent / "idedata" / f"{name}.json",
|
||||
# Regular builds, invoked from the config dir or from anywhere
|
||||
Path.cwd() / ".esphome" / "idedata" / f"{name}.json",
|
||||
Path.home() / ".esphome" / "idedata" / f"{name}.json",
|
||||
]
|
||||
|
||||
|
||||
def find_idedata_path(build_path: Path) -> Path | None:
|
||||
"""Locate the idedata JSON belonging to an ESPHome build directory.
|
||||
|
||||
Args:
|
||||
build_path: Path to an ESPHome build directory
|
||||
|
||||
Returns:
|
||||
Path to the idedata JSON, or None if it was not found
|
||||
"""
|
||||
for candidate in idedata_candidates(build_path):
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _find_in_platformio_packages(tool_name: str) -> str | None:
|
||||
"""Search for a tool in PlatformIO package directories.
|
||||
|
||||
|
||||
+35
-2
@@ -7,7 +7,7 @@ and compiled directly: ``esphome compile my_device.esphomebundle.tar.gz``
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
import io
|
||||
import json
|
||||
@@ -32,6 +32,8 @@ from esphome.core import CORE, EsphomeError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "bundle"
|
||||
|
||||
BUNDLE_EXTENSION = ".esphomebundle.tar.gz"
|
||||
MANIFEST_FILENAME = "manifest.json"
|
||||
CURRENT_MANIFEST_VERSION = 1
|
||||
@@ -120,6 +122,32 @@ def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]:
|
||||
return keys
|
||||
|
||||
|
||||
@dataclass
|
||||
class BundleData:
|
||||
"""Files components asked to include, keyed under DOMAIN in CORE.data."""
|
||||
|
||||
extra_files: list[Path] = field(default_factory=list)
|
||||
|
||||
|
||||
def _get_data() -> BundleData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = BundleData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def add_bundle_file(path: Path) -> None:
|
||||
"""Register a file that a bundle must include.
|
||||
|
||||
Bundle discovery walks the validated config, so it only finds files the config
|
||||
names. Components call this during validation for files it cannot see, such as a
|
||||
file that is referenced from inside another file.
|
||||
|
||||
A relative path is taken as relative to the config directory. Files outside the
|
||||
config directory are skipped when the bundle is built.
|
||||
"""
|
||||
_get_data().extra_files.append(CORE.relative_config_path(path))
|
||||
|
||||
|
||||
@dataclass
|
||||
class BundleFile:
|
||||
"""A file to include in the bundle."""
|
||||
@@ -286,13 +314,18 @@ class ConfigBundleCreator:
|
||||
with known file extensions are also resolved and checked.
|
||||
|
||||
Core ESPHome concepts that use relative paths or directories
|
||||
are handled explicitly.
|
||||
are handled explicitly. Files the config does not name at all are
|
||||
registered by their component with add_bundle_file().
|
||||
"""
|
||||
config = self._config
|
||||
|
||||
# Generic walk: find all file paths in the validated config
|
||||
self._walk_config_for_files(config)
|
||||
|
||||
# Files registered by components during validation
|
||||
for extra_file in _get_data().extra_files:
|
||||
self._add_file(extra_file)
|
||||
|
||||
# --- Core ESPHome concepts needing explicit handling ---
|
||||
|
||||
# esphome.includes / includes_c - can be relative paths and directories
|
||||
|
||||
@@ -1746,12 +1746,6 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
|
||||
this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
// TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
|
||||
if (!this->client_supports_api_version(1, 14)) {
|
||||
ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
}
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 14;
|
||||
|
||||
@@ -18,7 +18,7 @@ with warnings.catch_warnings():
|
||||
import contextlib
|
||||
|
||||
from esphome.const import CONF_KEY, CONF_PORT, __version__
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.core import CORE
|
||||
from esphome.util import safe_print
|
||||
|
||||
from . import CONF_ENCRYPTION
|
||||
@@ -36,15 +36,17 @@ class _LogLineProcessor:
|
||||
"""Feeds incoming log lines to the stack-trace decoder.
|
||||
|
||||
Two responsibilities beyond just calling the decoder:
|
||||
1. Catch EsphomeError. on_log runs inside an asyncio protocol
|
||||
callback; if an exception escapes, the loop tears the transport
|
||||
down with "Fatal error: protocol.data_received() call failed."
|
||||
and ReconnectLogic immediately reconnects, the device replays
|
||||
the same crash trace, and we loop forever.
|
||||
2. Disable decoding after the first failure. _decode_pc shells out
|
||||
to PlatformIO via _run_idedata, which is expensive; a single
|
||||
crash dump can contain many PC/BT lines and we don't want to
|
||||
retry the failing subprocess for each one.
|
||||
1. Catch everything the decoder can raise. aioesphomeapi isolates
|
||||
exceptions raised by log handlers, so an escaping one no longer
|
||||
kills the session, but it does log a full traceback per line. A
|
||||
crash dump carries a PC line plus one per backtrace frame, so the
|
||||
tracebacks bury the dump the user is trying to read. Decoding is a
|
||||
diagnostic nicety; nothing it raises is worth that noise.
|
||||
2. Disable decoding after the first failure. _decode_pc shells out to
|
||||
the toolchain to resolve addr2line, which is expensive; a single
|
||||
crash dump can contain many PC/BT lines and we don't want to retry
|
||||
the failing subprocess for each one. This only works if every
|
||||
failure is caught, which is why 1 is not narrowed to EsphomeError.
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict[str, Any], platform_handler: Any | None) -> None:
|
||||
@@ -61,12 +63,13 @@ class _LogLineProcessor:
|
||||
self.backtrace_state = self._platform_handler(
|
||||
self._config, raw_line, self.backtrace_state
|
||||
)
|
||||
except EsphomeError as exc:
|
||||
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
|
||||
self._decode_enabled = False
|
||||
self.backtrace_state = False
|
||||
# _run_idedata raises EsphomeError with no message; fall back
|
||||
# to a generic explanation when str(exc) is empty.
|
||||
detail = str(exc) or "build artifacts not found locally"
|
||||
_LOGGER.debug("Stack-trace decoding failed", exc_info=True)
|
||||
_LOGGER.warning(
|
||||
"Crash trace decoding unavailable: %s. "
|
||||
"Run 'esphome compile' for this device to enable PC decoding.",
|
||||
@@ -152,6 +155,9 @@ async def async_run_logs(
|
||||
name=name,
|
||||
subscribe_states=subscribe_states,
|
||||
allow_plaintext_fallback=True,
|
||||
# A top-level ``deep_sleep:`` block means the device is only awake
|
||||
# briefly; cap the reconnect backoff so a wake window is not missed.
|
||||
deep_sleep="deep_sleep" in config,
|
||||
)
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
|
||||
@@ -7,6 +7,7 @@ AQICalculatorType = aqi_ns.enum("AQICalculatorType")
|
||||
|
||||
CONF_AQI = "aqi"
|
||||
CONF_CALCULATION_TYPE = "calculation_type"
|
||||
CONF_EXTENDED_RANGE = "extended_range"
|
||||
|
||||
AQI_CALCULATION_TYPE = {
|
||||
"CAQI": AQICalculatorType.CAQI_TYPE,
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace esphome::aqi {
|
||||
|
||||
class AbstractAQICalculator {
|
||||
public:
|
||||
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
|
||||
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) = 0;
|
||||
};
|
||||
|
||||
} // namespace esphome::aqi
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace esphome::aqi {
|
||||
|
||||
class AQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID, extended_range);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID, extended_range);
|
||||
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
|
||||
// extended_range lets the index run past the standard maximum, so clamp to the sensor's range.
|
||||
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
|
||||
return static_cast<uint16_t>(std::lround(aqi));
|
||||
}
|
||||
|
||||
@@ -30,7 +32,7 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
{35.5f, 55.5f},
|
||||
{55.5f, 125.5f},
|
||||
{125.5f, 225.5f},
|
||||
{225.5f, std::numeric_limits<float>::max()}
|
||||
{225.5f, 500.4f} // EPA 2024: AQI 301-500 maps to PM2.5 225.5-500.4 ug/m3
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -41,11 +43,11 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
{155.0f, 255.0f},
|
||||
{255.0f, 355.0f},
|
||||
{355.0f, 425.0f},
|
||||
{425.0f, std::numeric_limits<float>::max()}
|
||||
{425.0f, 604.0f} // EPA: AQI 301-500 maps to PM10 425-604 ug/m3 (top of the 401-500 band)
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2], bool extended_range) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1.0f;
|
||||
@@ -55,14 +57,22 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
float index = (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
|
||||
// Concentrations above the highest breakpoint run the linear fit past aqi_hi. By default we
|
||||
// clamp to the standard maximum; with extended_range we keep the extrapolated "over-range"
|
||||
// value so heavy pollution reports numbers beyond what the standard defines.
|
||||
if (grid_index == NUM_LEVELS - 1 && !extended_range && index > aqi_hi) {
|
||||
return aqi_hi;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
const bool in_range =
|
||||
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
|
||||
: (value < array[i][1])); // others exclusive on hi
|
||||
// The top band is open-ended: any value at or above its lower breakpoint falls into it,
|
||||
// and calculate_index() decides whether to clamp or extrapolate.
|
||||
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
|
||||
if (in_range) {
|
||||
return i;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ void AQISensor::setup() {
|
||||
void AQISensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AQI Sensor:");
|
||||
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
|
||||
ESP_LOGCONFIG(TAG, " Extended Range: %s", this->extended_range_ ? "enabled" : "disabled");
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
|
||||
}
|
||||
@@ -44,7 +45,7 @@ void AQISensor::calculate_aqi_() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
|
||||
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_, this->extended_range_);
|
||||
this->publish_state(aqi);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class AQISensor final : public sensor::Sensor, public Component {
|
||||
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
|
||||
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
|
||||
void set_extended_range(bool extended_range) { this->extended_range_ = extended_range; }
|
||||
|
||||
protected:
|
||||
void calculate_aqi_();
|
||||
@@ -21,6 +22,7 @@ class AQISensor final : public sensor::Sensor, public Component {
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||
AQICalculatorType aqi_calc_type_{AQI_TYPE};
|
||||
bool extended_range_{false};
|
||||
AQICalculatorFactory aqi_calculator_factory_;
|
||||
|
||||
float pm_2_5_value_{NAN};
|
||||
|
||||
@@ -9,25 +9,28 @@ namespace esphome::aqi {
|
||||
|
||||
class CAQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
// The CAQI (CITEAIR) scale defines no maximum: its top "Very high" class is simply ">100". We
|
||||
// therefore always extrapolate the top band past 100 without limit, so the extended_range flag
|
||||
// (which lifts the AQI calculator's fixed 500 cap) has no meaning here and is ignored.
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool /*extended_range*/) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
|
||||
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
|
||||
return static_cast<uint16_t>(std::lround(aqi));
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr int NUM_LEVELS = 5;
|
||||
static constexpr int NUM_LEVELS = 4;
|
||||
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}};
|
||||
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||
// clang-format off
|
||||
{0.0f, 15.1f},
|
||||
{15.1f, 30.1f},
|
||||
{30.1f, 55.1f},
|
||||
{55.1f, 110.1f},
|
||||
{110.1f, std::numeric_limits<float>::max()}
|
||||
{55.1f, 110.1f}
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -36,8 +39,7 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
{0.0f, 25.1f},
|
||||
{25.1f, 50.1f},
|
||||
{50.1f, 90.1f},
|
||||
{90.1f, 180.1f},
|
||||
{180.1f, std::numeric_limits<float>::max()}
|
||||
{90.1f, 180.1f}
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -52,14 +54,15 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
// The top band is open-ended (see get_grid_index), so for concentrations above the last
|
||||
// breakpoint this linear fit extrapolates past 100 unbounded, matching CAQI's open ">100" class.
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
const bool in_range =
|
||||
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
|
||||
: (value < array[i][1])); // others exclusive on hi
|
||||
// The top band is open-ended: any value at or above its lower breakpoint falls into it.
|
||||
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
|
||||
if (in_range) {
|
||||
return i;
|
||||
}
|
||||
|
||||
@@ -8,14 +8,25 @@ from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
|
||||
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, CONF_EXTENDED_RANGE, aqi_ns
|
||||
|
||||
CODEOWNERS = ["@jasstrong"]
|
||||
DEPENDENCIES = ["sensor"]
|
||||
|
||||
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
def _validate_extended_range(config):
|
||||
if CONF_EXTENDED_RANGE in config and config[CONF_CALCULATION_TYPE] == "CAQI":
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_EXTENDED_RANGE}' is not supported with 'calculation_type: CAQI'. "
|
||||
"CAQI has no maximum value by specification, so it is always reported unbounded.",
|
||||
[CONF_EXTENDED_RANGE],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
AQISensor,
|
||||
accuracy_decimals=0,
|
||||
@@ -29,9 +40,11 @@ CONFIG_SCHEMA = (
|
||||
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
|
||||
AQI_CALCULATION_TYPE, upper=True
|
||||
),
|
||||
cv.Optional(CONF_EXTENDED_RANGE): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
_validate_extended_range,
|
||||
)
|
||||
|
||||
|
||||
@@ -46,3 +59,4 @@ async def to_code(config):
|
||||
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
|
||||
|
||||
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
|
||||
cg.add(var.set_extended_range(config.get(CONF_EXTENDED_RANGE, False)))
|
||||
|
||||
@@ -24,11 +24,7 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
|
||||
|
||||
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value;
|
||||
if (write(®, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Writing register failed!");
|
||||
return 0;
|
||||
}
|
||||
if (read(&value, 1) != i2c::ERROR_OK) {
|
||||
if (!this->read_byte(reg, &value)) {
|
||||
ESP_LOGW(TAG, "Reading register failed!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -37,15 +37,15 @@ namespace esphome::beken_spi_led_strip {
|
||||
|
||||
static const char *const TAG = "beken_spi_led_strip";
|
||||
|
||||
struct spi_data_t {
|
||||
struct SpiData {
|
||||
SemaphoreHandle_t dma_tx_semaphore;
|
||||
volatile bool tx_in_progress;
|
||||
bool first_run;
|
||||
};
|
||||
|
||||
static spi_data_t *spi_data = nullptr;
|
||||
static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static void set_spi_ctrl_register(unsigned long bit, bool val) {
|
||||
static void set_spi_ctrl_register(uint32_t bit, bool val) {
|
||||
uint32_t value = REG_READ(SPI_CTRL);
|
||||
if (val == 0) {
|
||||
value &= ~bit;
|
||||
@@ -55,7 +55,7 @@ static void set_spi_ctrl_register(unsigned long bit, bool val) {
|
||||
REG_WRITE(SPI_CTRL, value);
|
||||
}
|
||||
|
||||
static void set_spi_config_register(unsigned long bit, bool val) {
|
||||
static void set_spi_config_register(uint32_t bit, bool val) {
|
||||
uint32_t value = REG_READ(SPI_CONFIG);
|
||||
if (val == 0) {
|
||||
value &= ~bit;
|
||||
@@ -67,7 +67,7 @@ static void set_spi_config_register(unsigned long bit, bool val) {
|
||||
|
||||
void spi_dma_tx_enable(bool enable) {
|
||||
GDMA_CFG_ST en_cfg;
|
||||
set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
|
||||
set_spi_config_register(SPI_TX_EN, enable);
|
||||
en_cfg.channel = SPI_TX_DMA_CHANNEL;
|
||||
en_cfg.param = enable ? 1 : 0;
|
||||
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
|
||||
@@ -110,13 +110,13 @@ static void spi_set_clock(uint32_t max_hz) {
|
||||
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
|
||||
param |= (div << SPI_CKR_POSI);
|
||||
REG_WRITE(SPI_CTRL, param);
|
||||
ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
|
||||
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / div);
|
||||
}
|
||||
|
||||
void spi_dma_tx_finish_callback(unsigned int param) {
|
||||
spi_data->tx_in_progress = false;
|
||||
xSemaphoreGive(spi_data->dma_tx_semaphore);
|
||||
spi_dma_tx_enable(0);
|
||||
spi_dma_tx_enable(false);
|
||||
}
|
||||
|
||||
void BekenSPILEDStripLightOutput::setup() {
|
||||
@@ -161,7 +161,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
|
||||
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
|
||||
if (spi_data == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate spi_data!");
|
||||
this->mark_failed();
|
||||
@@ -177,20 +177,20 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
|
||||
spi_data->first_run = true;
|
||||
|
||||
set_spi_ctrl_register(MSTEN, 0);
|
||||
set_spi_ctrl_register(BIT_WDTH, 0);
|
||||
set_spi_ctrl_register(MSTEN, false);
|
||||
set_spi_ctrl_register(BIT_WDTH, false);
|
||||
spi_set_clock(this->spi_frequency_);
|
||||
set_spi_ctrl_register(CKPOL, 0);
|
||||
set_spi_ctrl_register(CKPHA, 0);
|
||||
set_spi_ctrl_register(MSTEN, 1);
|
||||
set_spi_ctrl_register(SPIEN, 1);
|
||||
set_spi_ctrl_register(CKPOL, false);
|
||||
set_spi_ctrl_register(CKPHA, false);
|
||||
set_spi_ctrl_register(MSTEN, true);
|
||||
set_spi_ctrl_register(SPIEN, true);
|
||||
|
||||
set_spi_ctrl_register(TXINT_EN, 0);
|
||||
set_spi_ctrl_register(RXINT_EN, 0);
|
||||
set_spi_config_register(SPI_TX_FINISH_EN, 1);
|
||||
set_spi_config_register(SPI_RX_FINISH_EN, 1);
|
||||
set_spi_ctrl_register(RXOVR_EN, 0);
|
||||
set_spi_ctrl_register(TXOVR_EN, 0);
|
||||
set_spi_ctrl_register(TXINT_EN, false);
|
||||
set_spi_ctrl_register(RXINT_EN, false);
|
||||
set_spi_config_register(SPI_TX_FINISH_EN, true);
|
||||
set_spi_config_register(SPI_RX_FINISH_EN, true);
|
||||
set_spi_ctrl_register(RXOVR_EN, false);
|
||||
set_spi_ctrl_register(TXOVR_EN, false);
|
||||
|
||||
value = REG_READ(SPI_CTRL);
|
||||
value &= ~CTRL_NSSMD_3;
|
||||
@@ -199,7 +199,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
|
||||
value = GFUNC_MODE_SPI_DMA;
|
||||
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
|
||||
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
|
||||
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
|
||||
|
||||
GDMA_CFG_ST en_cfg;
|
||||
GDMACFG_TPYES_ST init_cfg;
|
||||
@@ -210,7 +210,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
init_cfg.dstptr_incr = 0;
|
||||
init_cfg.srcptr_incr = 1;
|
||||
init_cfg.src_start_addr = this->dma_buf_;
|
||||
init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
|
||||
init_cfg.dst_start_addr = (void *) SPI_DAT; // NOLINT(performance-no-int-to-ptr) SPI_DMA_REG4_TXFIFO
|
||||
init_cfg.channel = SPI_TX_DMA_CHANNEL;
|
||||
init_cfg.prio = 0; // 10
|
||||
init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
|
||||
@@ -230,7 +230,7 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
en_cfg.param = 0;
|
||||
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
|
||||
|
||||
spi_dma_tx_enable(0);
|
||||
spi_dma_tx_enable(false);
|
||||
|
||||
value = REG_READ(SPI_CONFIG);
|
||||
value &= ~(0xFFF << 8);
|
||||
@@ -247,7 +247,8 @@ void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uin
|
||||
void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
|
||||
// protect from refreshing too often
|
||||
uint32_t now = micros();
|
||||
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
if (this->max_refresh_rate_.has_value() && *this->max_refresh_rate_ != 0 &&
|
||||
(now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
// try again next loop iteration, so that this change won't get lost
|
||||
this->schedule_show();
|
||||
return;
|
||||
@@ -293,7 +294,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
|
||||
}
|
||||
|
||||
spi_data->first_run = false;
|
||||
spi_dma_tx_enable(1);
|
||||
spi_dma_tx_enable(true);
|
||||
|
||||
this->status_clear_warning();
|
||||
}
|
||||
@@ -376,7 +377,7 @@ void BekenSPILEDStripLightOutput::dump_config() {
|
||||
" RGB Order: %s\n"
|
||||
" Max refresh rate: %" PRIu32 "\n"
|
||||
" Number of LEDs: %u",
|
||||
rgb_order, *this->max_refresh_rate_, this->num_leds_);
|
||||
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
|
||||
}
|
||||
|
||||
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
@@ -245,7 +245,9 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
service_resp.characteristics.init(total_char_count);
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
while (true) { // characteristics
|
||||
// Bound by total_char_count: the vector is sized for it, and a malicious peripheral
|
||||
// can make enumeration return more entries than the count query reported
|
||||
while (char_offset < total_char_count) { // characteristics
|
||||
uint16_t char_count = 1;
|
||||
esp_gatt_status_t char_status =
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
|
||||
@@ -287,7 +289,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
characteristic_resp.descriptors.init(total_desc_count);
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (true) { // descriptors
|
||||
while (desc_offset < total_desc_count) { // descriptors
|
||||
uint16_t desc_count = 1;
|
||||
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
|
||||
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||
|
||||
@@ -21,6 +21,7 @@ MULTI_CONF = True
|
||||
|
||||
ns = cg.esphome_ns.namespace("cc1101")
|
||||
CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice)
|
||||
CC1101Listener = ns.class_("CC1101Listener")
|
||||
|
||||
# Config keys
|
||||
CONF_RX_ATTENUATION = "rx_attenuation"
|
||||
@@ -48,6 +49,15 @@ CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk"
|
||||
CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
|
||||
CONF_FREEZE = "freeze"
|
||||
CONF_HYST_LEVEL = "hyst_level"
|
||||
CONF_FOC_BS_CS_GATE = "foc_bs_cs_gate"
|
||||
CONF_FOC_LIMIT = "foc_limit"
|
||||
CONF_FOC_PRE_K = "foc_pre_k"
|
||||
CONF_FOC_POST_K = "foc_post_k"
|
||||
CONF_BS_LIMIT = "bs_limit"
|
||||
CONF_BS_PRE_KI = "bs_pre_ki"
|
||||
CONF_BS_PRE_KP = "bs_pre_kp"
|
||||
CONF_BS_POST_KI = "bs_post_ki"
|
||||
CONF_BS_POST_KP = "bs_post_kp"
|
||||
|
||||
# Packet mode config keys
|
||||
CONF_PACKET_MODE = "packet_mode"
|
||||
@@ -161,6 +171,64 @@ HYST_LEVEL = {
|
||||
"High": HystLevel.HYST_LEVEL_HIGH,
|
||||
}
|
||||
|
||||
FocLimit = ns.enum("FocLimit", True)
|
||||
FOC_LIMIT = {
|
||||
"Disabled": FocLimit.FOC_LIMIT_DISABLED,
|
||||
"BW/8": FocLimit.FOC_LIMIT_BW_8,
|
||||
"BW/4": FocLimit.FOC_LIMIT_BW_4,
|
||||
"BW/2": FocLimit.FOC_LIMIT_BW_2,
|
||||
}
|
||||
|
||||
FocPreK = ns.enum("FocPreK", True)
|
||||
FOC_PRE_K = {
|
||||
"K": FocPreK.FOC_PRE_K_K,
|
||||
"2K": FocPreK.FOC_PRE_K_2K,
|
||||
"3K": FocPreK.FOC_PRE_K_3K,
|
||||
"4K": FocPreK.FOC_PRE_K_4K,
|
||||
}
|
||||
|
||||
FocPostK = ns.enum("FocPostK", True)
|
||||
FOC_POST_K = {
|
||||
"Same": FocPostK.FOC_POST_K_SAME,
|
||||
"K/2": FocPostK.FOC_POST_K_K_2,
|
||||
}
|
||||
|
||||
BsLimit = ns.enum("BsLimit", True)
|
||||
BS_LIMIT = {
|
||||
"Disabled": BsLimit.BS_LIMIT_DISABLED,
|
||||
"3.125%": BsLimit.BS_LIMIT_3P125_PERCENT,
|
||||
"6.25%": BsLimit.BS_LIMIT_6P25_PERCENT,
|
||||
"12.5%": BsLimit.BS_LIMIT_12P5_PERCENT,
|
||||
}
|
||||
|
||||
BsPreKi = ns.enum("BsPreKi", True)
|
||||
BS_PRE_KI = {
|
||||
"KI": BsPreKi.BS_PRE_KI_KI,
|
||||
"2KI": BsPreKi.BS_PRE_KI_2KI,
|
||||
"3KI": BsPreKi.BS_PRE_KI_3KI,
|
||||
"4KI": BsPreKi.BS_PRE_KI_4KI,
|
||||
}
|
||||
|
||||
BsPreKp = ns.enum("BsPreKp", True)
|
||||
BS_PRE_KP = {
|
||||
"KP": BsPreKp.BS_PRE_KP_KP,
|
||||
"2KP": BsPreKp.BS_PRE_KP_2KP,
|
||||
"3KP": BsPreKp.BS_PRE_KP_3KP,
|
||||
"4KP": BsPreKp.BS_PRE_KP_4KP,
|
||||
}
|
||||
|
||||
BsPostKi = ns.enum("BsPostKi", True)
|
||||
BS_POST_KI = {
|
||||
"Same": BsPostKi.BS_POST_KI_SAME,
|
||||
"KI/2": BsPostKi.BS_POST_KI_KI_2,
|
||||
}
|
||||
|
||||
BsPostKp = ns.enum("BsPostKp", True)
|
||||
BS_POST_KP = {
|
||||
"Same": BsPostKp.BS_POST_KP_SAME,
|
||||
"KP": BsPostKp.BS_POST_KP_KP,
|
||||
}
|
||||
|
||||
# Optional settings to generate setter calls for
|
||||
CONFIG_MAP = {
|
||||
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
|
||||
@@ -214,6 +282,15 @@ CONFIG_MAP = {
|
||||
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
|
||||
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
|
||||
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
|
||||
cv.Optional(CONF_FOC_BS_CS_GATE): cv.boolean,
|
||||
cv.Optional(CONF_FOC_LIMIT): cv.enum(FOC_LIMIT, upper=False),
|
||||
cv.Optional(CONF_FOC_PRE_K): cv.enum(FOC_PRE_K, upper=False),
|
||||
cv.Optional(CONF_FOC_POST_K): cv.enum(FOC_POST_K, upper=False),
|
||||
cv.Optional(CONF_BS_LIMIT): cv.enum(BS_LIMIT, upper=False),
|
||||
cv.Optional(CONF_BS_PRE_KI): cv.enum(BS_PRE_KI, upper=False),
|
||||
cv.Optional(CONF_BS_PRE_KP): cv.enum(BS_PRE_KP, upper=False),
|
||||
cv.Optional(CONF_BS_POST_KI): cv.enum(BS_POST_KI, upper=False),
|
||||
cv.Optional(CONF_BS_POST_KP): cv.enum(BS_POST_KP, upper=False),
|
||||
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
|
||||
@@ -672,6 +672,69 @@ void CC1101Component::set_hyst_level(HystLevel value) {
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_foc_bs_cs_gate(bool value) {
|
||||
this->state_.FOC_BS_CS_GATE = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FOCCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_foc_limit(FocLimit value) {
|
||||
this->state_.FOC_LIMIT = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FOCCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_foc_pre_k(FocPreK value) {
|
||||
this->state_.FOC_PRE_K = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FOCCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_foc_post_k(FocPostK value) {
|
||||
this->state_.FOC_POST_K = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::FOCCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_bs_limit(BsLimit value) {
|
||||
this->state_.BS_LIMIT = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::BSCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_bs_pre_ki(BsPreKi value) {
|
||||
this->state_.BS_PRE_KI = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::BSCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_bs_pre_kp(BsPreKp value) {
|
||||
this->state_.BS_PRE_KP = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::BSCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_bs_post_ki(BsPostKi value) {
|
||||
this->state_.BS_POST_KI = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::BSCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_bs_post_kp(BsPostKp value) {
|
||||
this->state_.BS_POST_KP = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::BSCFG);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_packet_mode(bool value) {
|
||||
this->state_.PKT_FORMAT =
|
||||
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
|
||||
|
||||
@@ -71,6 +71,17 @@ class CC1101Component final : public Component,
|
||||
void set_wait_time(WaitTime value);
|
||||
void set_hyst_level(HystLevel value);
|
||||
|
||||
// Frequency offset compensation and bit synchronization settings
|
||||
void set_foc_bs_cs_gate(bool value);
|
||||
void set_foc_limit(FocLimit value);
|
||||
void set_foc_pre_k(FocPreK value);
|
||||
void set_foc_post_k(FocPostK value);
|
||||
void set_bs_limit(BsLimit value);
|
||||
void set_bs_pre_ki(BsPreKi value);
|
||||
void set_bs_pre_kp(BsPreKp value);
|
||||
void set_bs_post_ki(BsPostKi value);
|
||||
void set_bs_post_kp(BsPostKp value);
|
||||
|
||||
// Packet mode settings
|
||||
void set_packet_mode(bool value);
|
||||
void set_packet_length(uint8_t value);
|
||||
|
||||
@@ -231,6 +231,56 @@ enum class HystLevel : uint8_t {
|
||||
HYST_LEVEL_HIGH,
|
||||
};
|
||||
|
||||
enum class FocLimit : uint8_t {
|
||||
FOC_LIMIT_DISABLED,
|
||||
FOC_LIMIT_BW_8,
|
||||
FOC_LIMIT_BW_4,
|
||||
FOC_LIMIT_BW_2,
|
||||
};
|
||||
|
||||
enum class FocPreK : uint8_t {
|
||||
FOC_PRE_K_K,
|
||||
FOC_PRE_K_2K,
|
||||
FOC_PRE_K_3K,
|
||||
FOC_PRE_K_4K,
|
||||
};
|
||||
|
||||
enum class FocPostK : uint8_t {
|
||||
FOC_POST_K_SAME,
|
||||
FOC_POST_K_K_2,
|
||||
};
|
||||
|
||||
enum class BsLimit : uint8_t {
|
||||
BS_LIMIT_DISABLED,
|
||||
BS_LIMIT_3P125_PERCENT,
|
||||
BS_LIMIT_6P25_PERCENT,
|
||||
BS_LIMIT_12P5_PERCENT,
|
||||
};
|
||||
|
||||
enum class BsPreKi : uint8_t {
|
||||
BS_PRE_KI_KI,
|
||||
BS_PRE_KI_2KI,
|
||||
BS_PRE_KI_3KI,
|
||||
BS_PRE_KI_4KI,
|
||||
};
|
||||
|
||||
enum class BsPreKp : uint8_t {
|
||||
BS_PRE_KP_KP,
|
||||
BS_PRE_KP_2KP,
|
||||
BS_PRE_KP_3KP,
|
||||
BS_PRE_KP_4KP,
|
||||
};
|
||||
|
||||
enum class BsPostKi : uint8_t {
|
||||
BS_POST_KI_SAME,
|
||||
BS_POST_KI_KI_2,
|
||||
};
|
||||
|
||||
enum class BsPostKp : uint8_t {
|
||||
BS_POST_KP_SAME,
|
||||
BS_POST_KP_KP,
|
||||
};
|
||||
|
||||
enum class PacketFormat : uint8_t {
|
||||
PACKET_FORMAT_FIFO,
|
||||
PACKET_FORMAT_SYNC_SERIAL,
|
||||
|
||||
@@ -28,7 +28,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
ESP_LOGD(TAG,
|
||||
"LibreTiny debug info:\n"
|
||||
" Version: %s\n"
|
||||
" Chip: %s (%04x) @ %u MHz\n"
|
||||
" Chip: %s (%04x) @ %" PRIu32 " MHz\n"
|
||||
" Chip ID: 0x%06" PRIX32 "\n"
|
||||
" Board: %s\n"
|
||||
" Flash: %" PRIu32 " KiB\n"
|
||||
@@ -38,7 +38,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
||||
|
||||
pos = buf_append_str(buf, size, pos, "|Version: ");
|
||||
pos = buf_append_str(buf, size, pos, LT_BANNER_STR + 10);
|
||||
pos = buf_append_str(buf, size, pos, <_BANNER_STR[10]);
|
||||
pos = buf_append_str(buf, size, pos, "|Reset Reason: ");
|
||||
pos = buf_append_str(buf, size, pos, reset_reason);
|
||||
pos = buf_append_str(buf, size, pos, "|Chip Name: ");
|
||||
|
||||
@@ -74,7 +74,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
uint32_t cpu_freq = ::rp2040.f_cpu();
|
||||
uint32_t cpu_freq = RP2040::f_cpu();
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
|
||||
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from esphome.const import (
|
||||
CONF_SECOND,
|
||||
CONF_SLEEP_DURATION,
|
||||
CONF_TIME_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WAKEUP_PIN,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
@@ -234,6 +235,15 @@ EXT1_WAKEUP_MODES = {
|
||||
}
|
||||
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
|
||||
|
||||
WakeupCause = deep_sleep_ns.enum("WakeupCause")
|
||||
WakeTrigger = deep_sleep_ns.class_(
|
||||
"WakeTrigger", automation.Trigger.template(WakeupCause), cg.Component
|
||||
)
|
||||
Ext1WakeTrigger = deep_sleep_ns.class_(
|
||||
"Ext1WakeTrigger", automation.Trigger.template(), cg.Component
|
||||
)
|
||||
|
||||
CONF_ON_WAKE = "on_wake"
|
||||
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
||||
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
||||
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||
@@ -256,6 +266,22 @@ WAKEUP_PIN_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
EXT1_WAKEUP_PIN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema, validate_pin_number_esp32
|
||||
),
|
||||
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Ext1WakeTrigger)}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Entries that are not in the {pin: ..., on_wake: ...} form are treated as a
|
||||
# bare pin config (the original syntax, e.g. a plain "GPIO5" or {number: 5}).
|
||||
validate_ext1_wakeup_pin = cv.maybe_simple_value(EXT1_WAKEUP_PIN_SCHEMA, key=CONF_PIN)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -282,8 +308,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PINS): cv.ensure_list(
|
||||
pins.internal_gpio_input_pin_schema,
|
||||
validate_pin_number_esp32,
|
||||
validate_ext1_wakeup_pin,
|
||||
),
|
||||
cv.Required(CONF_MODE): cv.All(
|
||||
cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
||||
@@ -292,6 +317,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ON_WAKE): cv.All(
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
|
||||
automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger)}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
|
||||
cv.only_on_esp32,
|
||||
esp32.only_on_variant(
|
||||
@@ -323,7 +354,7 @@ async def to_code(config):
|
||||
if CONF_WAKEUP_PIN in config:
|
||||
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
|
||||
if CORE.is_bk72xx:
|
||||
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
|
||||
cg.add(var.init_wakeup_pins(len(pins_as_list)))
|
||||
for item in pins_as_list:
|
||||
cg.add(
|
||||
var.add_wakeup_pin(
|
||||
@@ -362,16 +393,27 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
|
||||
|
||||
if CONF_ESP32_EXT1_WAKEUP in config:
|
||||
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
||||
if (ext1_conf := config.get(CONF_ESP32_EXT1_WAKEUP)) is not None:
|
||||
mask = 0
|
||||
for pin in conf[CONF_PINS]:
|
||||
mask |= 1 << pin[CONF_NUMBER]
|
||||
for pin_conf in ext1_conf[CONF_PINS]:
|
||||
number = pin_conf[CONF_PIN][CONF_NUMBER]
|
||||
mask |= 1 << number
|
||||
for wake_conf in pin_conf.get(CONF_ON_WAKE, []):
|
||||
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID], number)
|
||||
await cg.register_component(trigger, wake_conf)
|
||||
await automation.build_automation(trigger, [], wake_conf)
|
||||
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
|
||||
struct = cg.StructInitializer(
|
||||
Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE])
|
||||
Ext1Wakeup, ("mask", mask), ("wakeup_mode", ext1_conf[CONF_MODE])
|
||||
)
|
||||
cg.add(var.set_ext1_wakeup(struct))
|
||||
|
||||
for wake_conf in config.get(CONF_ON_WAKE, []):
|
||||
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID])
|
||||
await cg.register_component(trigger, wake_conf)
|
||||
await automation.build_automation(trigger, [(WakeupCause, "cause")], wake_conf)
|
||||
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
|
||||
|
||||
if CONF_TOUCH_WAKEUP in config:
|
||||
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
|
||||
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
|
||||
|
||||
@@ -7,6 +7,21 @@ namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep.bk72xx";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
switch (lt_get_reboot_reason()) {
|
||||
case REBOOT_REASON_SLEEP_GPIO:
|
||||
return WAKEUP_CAUSE_GPIO;
|
||||
case REBOOT_REASON_SLEEP_RTC:
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
case REBOOT_REASON_SLEEP_USB:
|
||||
return WAKEUP_CAUSE_UNKNOWN;
|
||||
default:
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
}
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {
|
||||
@@ -15,15 +30,15 @@ void DeepSleepComponent::dump_config_platform_() {
|
||||
}
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
|
||||
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
|
||||
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
|
||||
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pin_item) const {
|
||||
return (pin_item.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pin_item.wakeup_pin != nullptr &&
|
||||
!this->sleep_duration_.has_value() && (pin_item.wakeup_level == get_real_pin_state_(*pin_item.wakeup_pin)));
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::prepare_to_sleep_() {
|
||||
if (wakeup_pins_.size() > 0) {
|
||||
if (!this->wakeup_pins_.empty()) {
|
||||
for (WakeUpPinItem &item : this->wakeup_pins_) {
|
||||
if (pin_prevents_sleep_(item)) {
|
||||
if (this->pin_prevents_sleep_(item)) {
|
||||
// Defer deep sleep until inactive
|
||||
if (!this->next_enter_deep_sleep_) {
|
||||
this->status_set_warning();
|
||||
@@ -44,7 +59,7 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
item.wakeup_level = !item.wakeup_level;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
|
||||
ESP_LOGI(TAG, "Wake-up on P%u %s (%" PRId32 ")", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
|
||||
static_cast<int32_t>(item.wakeup_pin_mode));
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,65 @@ struct WakeupCauseToRunDuration {
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
/// Why the device woke from deep sleep. Passed to on_wake automations.
|
||||
enum WakeupCause : uint8_t {
|
||||
/// The device did not wake from deep sleep (for example a cold boot, reset or OTA restart).
|
||||
WAKEUP_CAUSE_NONE = 0,
|
||||
/// The device woke from deep sleep, but the source could not be identified.
|
||||
WAKEUP_CAUSE_UNKNOWN,
|
||||
/// The device was woken by the sleep timer.
|
||||
WAKEUP_CAUSE_TIMER,
|
||||
/// The device was woken by a GPIO pin (wakeup_pin or esp32_ext1_wakeup).
|
||||
WAKEUP_CAUSE_GPIO,
|
||||
/// The device was woken by a touch pad.
|
||||
WAKEUP_CAUSE_TOUCH,
|
||||
};
|
||||
|
||||
/// Return why the device woke from deep sleep. Implemented per platform.
|
||||
WakeupCause get_wakeup_cause();
|
||||
|
||||
/** Setup priority of on_wake triggers.
|
||||
*
|
||||
* Between restoring global variables (setup_priority::HARDWARE, 800) and on_boot automations at
|
||||
* their default priority (600), so on_wake automations can update state (e.g. globals) that
|
||||
* on_boot automations then use.
|
||||
*/
|
||||
inline constexpr float ON_WAKE_TRIGGER_SETUP_PRIORITY = 700.0f;
|
||||
|
||||
/// Fires once on boot when the device woke from deep sleep, with the wakeup cause.
|
||||
class WakeTrigger : public Trigger<WakeupCause>, public Component {
|
||||
public:
|
||||
void setup() override {
|
||||
const WakeupCause cause = get_wakeup_cause();
|
||||
if (cause != WAKEUP_CAUSE_NONE) {
|
||||
this->trigger(cause);
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
/// Fires once on boot when the device was woken from deep sleep by the given ext1 pin.
|
||||
class Ext1WakeTrigger : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit Ext1WakeTrigger(uint8_t pin) : pin_(pin) {}
|
||||
void setup() override {
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1 &&
|
||||
(esp_sleep_get_ext1_wakeup_status() & (1ULL << this->pin_))) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
template<typename... Ts> class EnterDeepSleepAction;
|
||||
|
||||
template<typename... Ts> class PreventDeepSleepAction;
|
||||
@@ -84,7 +143,7 @@ class DeepSleepComponent final : public Component {
|
||||
#endif // USE_ESP32
|
||||
|
||||
#if defined(USE_BK72XX)
|
||||
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
|
||||
void init_wakeup_pins(size_t capacity) { this->wakeup_pins_.init(capacity); }
|
||||
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
|
||||
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
|
||||
}
|
||||
@@ -132,7 +191,7 @@ class DeepSleepComponent final : public Component {
|
||||
bool should_teardown_();
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
|
||||
bool pin_prevents_sleep_(WakeUpPinItem &pin_item) const;
|
||||
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
|
||||
#endif // USE_BK72XX
|
||||
|
||||
|
||||
@@ -30,6 +30,25 @@ namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
switch (esp_sleep_get_wakeup_cause()) {
|
||||
case ESP_SLEEP_WAKEUP_EXT0:
|
||||
case ESP_SLEEP_WAKEUP_EXT1:
|
||||
case ESP_SLEEP_WAKEUP_GPIO:
|
||||
return WAKEUP_CAUSE_GPIO;
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
case ESP_SLEEP_WAKEUP_TOUCHPAD:
|
||||
return WAKEUP_CAUSE_TOUCH;
|
||||
case ESP_SLEEP_WAKEUP_UNDEFINED:
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
default:
|
||||
return WAKEUP_CAUSE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
||||
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
|
||||
|
||||
@@ -3,10 +3,27 @@
|
||||
|
||||
#include <Esp.h>
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
extern "C" {
|
||||
#include <user_interface.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
#ifdef USE_DEEP_SLEEP_ON_WAKE
|
||||
WakeupCause get_wakeup_cause() {
|
||||
// The ESP8266 can only wake from deep sleep through the RTC timer (via GPIO16 -> RST).
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
if (ESP.getResetInfoPtr()->reason == REASON_DEEP_SLEEP_AWAKE) {
|
||||
return WAKEUP_CAUSE_TIMER;
|
||||
}
|
||||
return WAKEUP_CAUSE_NONE;
|
||||
}
|
||||
#endif // USE_DEEP_SLEEP_ON_WAKE
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_SLEEP_PIN, CONF_TYPE
|
||||
|
||||
CODEOWNERS = ["@tomwellnitz"]
|
||||
MULTI_CONF = True
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_DS248X_ID = "ds248x_id"
|
||||
CONF_BUS_SLEEP = "bus_sleep"
|
||||
CONF_HUB_SLEEP = "hub_sleep"
|
||||
CONF_ACTIVE_PULLUP = "active_pullup"
|
||||
|
||||
CONF_RESET_LOW_TIME = "reset_low_time"
|
||||
CONF_MASTER_SAMPLE_TIME = "master_sample_time"
|
||||
CONF_WRITE_0_LOW_TIME = "write_0_low_time"
|
||||
CONF_RECOVERY_TIME = "recovery_time"
|
||||
CONF_ACTIVE_PULLUP_RESISTANCE = "active_pullup_resistance"
|
||||
|
||||
TYPE_DS2482_100 = "ds2482-100"
|
||||
TYPE_DS2482_101 = "ds2482-101"
|
||||
TYPE_DS2482_800 = "ds2482-800"
|
||||
TYPE_DS2484 = "ds2484"
|
||||
|
||||
CHANNEL_COUNTS = {
|
||||
TYPE_DS2482_100: 1,
|
||||
TYPE_DS2482_101: 1,
|
||||
TYPE_DS2482_800: 8,
|
||||
TYPE_DS2484: 1,
|
||||
}
|
||||
|
||||
ds248x_ns = cg.esphome_ns.namespace("ds248x")
|
||||
DS248xComponent = ds248x_ns.class_("DS248xComponent", cg.Component, i2c.I2CDevice)
|
||||
|
||||
|
||||
def _component_schema(*extras):
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DS248xComponent),
|
||||
cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
for extra in extras:
|
||||
schema = schema.extend(extra)
|
||||
return schema.extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x18))
|
||||
|
||||
|
||||
SLEEP_SCHEMA = {
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BUS_SLEEP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_HUB_SLEEP, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
DS2484_SCHEMA = {
|
||||
cv.Optional(CONF_RESET_LOW_TIME): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_MASTER_SAMPLE_TIME): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_WRITE_0_LOW_TIME): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_RECOVERY_TIME): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_ACTIVE_PULLUP_RESISTANCE): cv.enum(
|
||||
{
|
||||
# DS2484 Table 7: value codes 0-5 map to 500 ohm, 6-15 map to 1000 ohm.
|
||||
"500ohm": 0,
|
||||
"1000ohm": 6,
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
TYPE_DS2482_100: _component_schema(),
|
||||
TYPE_DS2482_101: _component_schema(SLEEP_SCHEMA),
|
||||
TYPE_DS2482_800: _component_schema(),
|
||||
TYPE_DS2484: _component_schema(SLEEP_SCHEMA, DS2484_SCHEMA),
|
||||
},
|
||||
key=CONF_TYPE,
|
||||
lower=True,
|
||||
)
|
||||
|
||||
|
||||
def get_channel_count(config):
|
||||
return CHANNEL_COUNTS[config[CONF_TYPE]]
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
|
||||
cg.add(var.set_channel_count(get_channel_count(config)))
|
||||
|
||||
if CONF_BUS_SLEEP in config:
|
||||
cg.add(var.set_bus_sleep(config[CONF_BUS_SLEEP]))
|
||||
if CONF_HUB_SLEEP in config:
|
||||
cg.add(var.set_hub_sleep(config[CONF_HUB_SLEEP]))
|
||||
|
||||
if CONF_RESET_LOW_TIME in config:
|
||||
cg.add(var.set_val_trstl(config[CONF_RESET_LOW_TIME]))
|
||||
if CONF_MASTER_SAMPLE_TIME in config:
|
||||
cg.add(var.set_val_tmsp(config[CONF_MASTER_SAMPLE_TIME]))
|
||||
if CONF_WRITE_0_LOW_TIME in config:
|
||||
cg.add(var.set_val_tw0l(config[CONF_WRITE_0_LOW_TIME]))
|
||||
if CONF_RECOVERY_TIME in config:
|
||||
cg.add(var.set_val_trec0(config[CONF_RECOVERY_TIME]))
|
||||
if CONF_ACTIVE_PULLUP_RESISTANCE in config:
|
||||
cg.add(var.set_val_rwpu(config[CONF_ACTIVE_PULLUP_RESISTANCE]))
|
||||
|
||||
if CONF_SLEEP_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
|
||||
cg.add(var.set_sleep_pin(pin))
|
||||
@@ -0,0 +1,320 @@
|
||||
#include "ds248x.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::ds248x {
|
||||
|
||||
static const char *const TAG = "ds248x";
|
||||
|
||||
void DS248xComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up DS248x...");
|
||||
|
||||
// Wake up device if sleep pin is configured
|
||||
if (this->sleep_pin_) {
|
||||
this->sleep_pin_->setup();
|
||||
this->sleep_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT);
|
||||
this->sleep_pin_->digital_write(true); // Wake up
|
||||
delay(1); // DS2482-101 Datasheet: tOSCWUP = 100μs (using 10x margin)
|
||||
}
|
||||
|
||||
// Probe device
|
||||
ESP_LOGD(TAG, "Probing DS248x...");
|
||||
uint8_t status = 0;
|
||||
if (this->read(&status, 1) == i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Device responded! Status: 0x%02x", status);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Device did not respond. Trying reset anyway...");
|
||||
}
|
||||
|
||||
if (!this->device_reset_()) {
|
||||
ESP_LOGW(TAG, "DS248x reset failed during setup!");
|
||||
}
|
||||
|
||||
// Configure device
|
||||
if (!this->device_configure_()) {
|
||||
ESP_LOGE(TAG, "DS248x configuration failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to Channel 0
|
||||
this->select_channel(0);
|
||||
|
||||
ESP_LOGI(TAG, "DS248x initialized successfully.");
|
||||
}
|
||||
|
||||
void DS248xComponent::on_shutdown() {
|
||||
if (this->sleep_pin_ && (this->hub_sleep_ || this->bus_sleep_)) {
|
||||
this->sleep_pin_->digital_write(false); // Sleep
|
||||
}
|
||||
}
|
||||
|
||||
void DS248xComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DS248x:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
ESP_LOGCONFIG(TAG, " Channel Count: %d", this->channel_count_);
|
||||
ESP_LOGCONFIG(TAG, " Active Pullup: %s", YESNO(this->active_pullup_));
|
||||
if (this->ds2484_mode_) {
|
||||
ESP_LOGCONFIG(TAG, " DS2484 Mode: enabled");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Internal Helpers ---
|
||||
|
||||
// Datasheet command durations are sub-2ms; allow a little margin before forcing recovery.
|
||||
static constexpr uint32_t BUSY_TIMEOUT_MS = 5;
|
||||
|
||||
bool DS248xComponent::set_read_pointer_(uint8_t ptr) { return this->write_byte(DS248X_COMMAND_SETREADPTR, ptr); }
|
||||
|
||||
bool DS248xComponent::wait_busy_() {
|
||||
uint32_t start = millis();
|
||||
do {
|
||||
uint8_t status;
|
||||
if (this->read(&status, 1) == i2c::ERROR_OK && !(status & DS248X_STATUS_BUSY))
|
||||
return true;
|
||||
delayMicroseconds(100);
|
||||
} while (millis() - start < BUSY_TIMEOUT_MS);
|
||||
ESP_LOGW(TAG, "DS248x busy timeout");
|
||||
bool recovered = this->device_reset_() && this->device_configure_();
|
||||
this->current_channel_ = -1;
|
||||
if (!recovered) {
|
||||
ESP_LOGE(TAG, "DS248x recovery failed after busy timeout");
|
||||
this->mark_failed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DS248xComponent::device_reset_() {
|
||||
ESP_LOGD(TAG, "Resetting device...");
|
||||
uint8_t cmd = DS248X_COMMAND_RESET;
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
uint8_t status;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
if (!(status & DS248X_STATUS_RST)) {
|
||||
ESP_LOGW(TAG, "Device reset failed (RST bit not set)");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->current_channel_ = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS248xComponent::device_configure_() {
|
||||
ESP_LOGD(TAG, "Configuring device...");
|
||||
|
||||
if (!this->write_config_()) {
|
||||
ESP_LOGW(TAG, "Config write/verify failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Configured successfully");
|
||||
|
||||
// DS2484 Configuration
|
||||
if (this->ds2484_mode_) {
|
||||
if (this->ds2484_trstl_ != DS2484_PARAM_UNSET &&
|
||||
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TRSTL, this->ds2484_trstl_))
|
||||
return false;
|
||||
if (this->ds2484_tmsp_ != DS2484_PARAM_UNSET &&
|
||||
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TMSP, this->ds2484_tmsp_))
|
||||
return false;
|
||||
if (this->ds2484_tw0l_ != DS2484_PARAM_UNSET &&
|
||||
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TW0L, this->ds2484_tw0l_))
|
||||
return false;
|
||||
if (this->ds2484_trec0_ != DS2484_PARAM_UNSET &&
|
||||
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TREC0, this->ds2484_trec0_))
|
||||
return false;
|
||||
if (this->ds2484_rwpu_ != DS2484_PARAM_UNSET &&
|
||||
!this->configure_ds2484_port_(DS2484_PORT_PARAM_RWPU, this->ds2484_rwpu_))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS248xComponent::configure_ds2484_port_(uint8_t param, uint8_t val) {
|
||||
uint8_t cmd = DS2484_COMMAND_ADJUSTPORT;
|
||||
// Control Byte format (DS2484 Table 6): P[2:0] in bits 7:5, OD in bit 4, VAL[3:0] in bits 3:0
|
||||
uint8_t data = ((param & 0x07) << 5) | (val & 0x0F);
|
||||
|
||||
// The DS2484 always acknowledges the Adjust 1-Wire Port control byte (datasheet "Adjust
|
||||
// 1-Wire Port"), so a successful write confirms the update. We deliberately do not read
|
||||
// back to verify: a single read of the Port Configuration register always returns the
|
||||
// fixed 8-byte report starting at Byte 1 (tRSTL standard speed), not the parameter that
|
||||
// was just written, so a per-parameter readback comparison would spuriously fail for
|
||||
// tMSP/tW0L/tREC0/RWPU.
|
||||
if (!this->write_byte(cmd, data)) {
|
||||
ESP_LOGW(TAG, "DS2484 port config failed (param %d)", param);
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->set_read_pointer_(DS248X_POINTER_STATUS);
|
||||
}
|
||||
|
||||
bool DS248xComponent::write_config_() {
|
||||
uint8_t config = 0;
|
||||
if (this->active_pullup_)
|
||||
config |= DS248X_CONFIG_ACTIVE_PULLUP;
|
||||
|
||||
// The DS248x only accepts the config byte if the upper nibble is the one's-complement of the lower nibble.
|
||||
uint8_t config_byte = (config & 0x0F) | ((~config & 0x0F) << 4);
|
||||
|
||||
if (!this->write_byte(DS248X_COMMAND_WRITECONFIG, config_byte)) {
|
||||
ESP_LOGW(TAG, "Failed to write config byte");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_CONFIG)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t read_config;
|
||||
if (this->read(&read_config, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Failed to read back config byte");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((read_config & 0x0F) != (config_byte & 0x0F)) {
|
||||
ESP_LOGW(TAG, "Config mismatch! Wrote 0x%02x, Read 0x%02x", config_byte, read_config);
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->set_read_pointer_(DS248X_POINTER_STATUS);
|
||||
}
|
||||
|
||||
// --- Channel Selection ---
|
||||
|
||||
// Channel select codes: write code -> expected read code
|
||||
static constexpr uint8_t CHANNEL_WRITE_CODES[8] = {0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87};
|
||||
static constexpr uint8_t CHANNEL_READ_CODES[8] = {0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87};
|
||||
|
||||
bool DS248xComponent::select_channel(uint8_t channel) {
|
||||
if (this->channel_count_ <= 1)
|
||||
return true;
|
||||
if (channel >= this->channel_count_)
|
||||
return false;
|
||||
|
||||
if (this->current_channel_ == channel)
|
||||
return true;
|
||||
|
||||
if (!this->write_byte(DS248X_COMMAND_CHANNELSELECT, CHANNEL_WRITE_CODES[channel])) {
|
||||
this->current_channel_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t read_code;
|
||||
if (this->read(&read_code, 1) != i2c::ERROR_OK) {
|
||||
this->current_channel_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_code != CHANNEL_READ_CODES[channel]) {
|
||||
ESP_LOGW(TAG, "Channel select failed! Expected 0x%02x, got 0x%02x", CHANNEL_READ_CODES[channel], read_code);
|
||||
this->current_channel_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
|
||||
return false;
|
||||
|
||||
this->current_channel_ = channel;
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 1-Wire Bus Operations ---
|
||||
|
||||
bool DS248xComponent::ow_reset(bool &presence) {
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
|
||||
return false;
|
||||
|
||||
uint8_t cmd = DS248X_COMMAND_RESETWIRE;
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
if (!this->wait_busy_()) {
|
||||
ESP_LOGW(TAG, "ow_reset: wait busy failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t status;
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "ow_reset: read status failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status & DS248X_STATUS_SD) {
|
||||
ESP_LOGW(TAG, "Short detected on 1-Wire bus!");
|
||||
return false;
|
||||
}
|
||||
|
||||
presence = (status & DS248X_STATUS_PPD);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS248xComponent::ow_write_byte(uint8_t byte) {
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
|
||||
return false;
|
||||
|
||||
if (!this->wait_busy_()) {
|
||||
ESP_LOGW(TAG, "Device busy before writing byte 0x%02x", byte);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t cmd[2] = {DS248X_COMMAND_WRITEBYTE, byte};
|
||||
if (this->write(cmd, 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed for byte 0x%02x", byte);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->wait_busy_()) {
|
||||
ESP_LOGW(TAG, "Timeout waiting for write byte to complete!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS248xComponent::ow_read_byte(uint8_t &byte) {
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
|
||||
return false;
|
||||
|
||||
uint8_t cmd = DS248X_COMMAND_READBYTE;
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
if (!this->wait_busy_())
|
||||
return false;
|
||||
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_DATA))
|
||||
return false;
|
||||
|
||||
if (this->read(&byte, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DS248xComponent::search_triplet(bool search_direction, uint8_t &status) {
|
||||
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
|
||||
return false;
|
||||
|
||||
// DS248x Datasheet: 1-Wire Triplet command requires 2 bytes:
|
||||
// Byte 1: Command code 0x78
|
||||
// Byte 2: Direction byte (bit 7 = V, search direction if discrepancy)
|
||||
uint8_t buffer[2] = {DS248X_COMMAND_TRIPLET, static_cast<uint8_t>(search_direction ? 0x80 : 0x00)};
|
||||
if (this->write(buffer, 2) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
if (!this->wait_busy_())
|
||||
return false;
|
||||
|
||||
if (this->read(&status, 1) != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::ds248x
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
// DS248x I2C-to-1-Wire Bridge Family
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-100.pdf
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-800.pdf
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2484.pdf
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::ds248x {
|
||||
|
||||
// DS248x I2C Commands
|
||||
static constexpr uint8_t DS248X_COMMAND_RESET = 0xF0;
|
||||
static constexpr uint8_t DS248X_COMMAND_SETREADPTR = 0xE1;
|
||||
static constexpr uint8_t DS248X_COMMAND_WRITECONFIG = 0xD2;
|
||||
static constexpr uint8_t DS248X_COMMAND_CHANNELSELECT = 0xC3;
|
||||
static constexpr uint8_t DS248X_COMMAND_RESETWIRE = 0xB4;
|
||||
static constexpr uint8_t DS248X_COMMAND_WRITEBYTE = 0xA5;
|
||||
static constexpr uint8_t DS248X_COMMAND_READBYTE = 0x96;
|
||||
static constexpr uint8_t DS248X_COMMAND_TRIPLET = 0x78;
|
||||
static constexpr uint8_t DS2484_COMMAND_ADJUSTPORT = 0xC3;
|
||||
|
||||
// DS2484 "Adjust 1-Wire Port" parameter codes (datasheet Table 6, control byte P[2:0])
|
||||
static constexpr uint8_t DS2484_PORT_PARAM_TRSTL = 0x0;
|
||||
static constexpr uint8_t DS2484_PORT_PARAM_TMSP = 0x1;
|
||||
static constexpr uint8_t DS2484_PORT_PARAM_TW0L = 0x2;
|
||||
static constexpr uint8_t DS2484_PORT_PARAM_TREC0 = 0x3;
|
||||
static constexpr uint8_t DS2484_PORT_PARAM_RWPU = 0x4;
|
||||
|
||||
// DS248x Status Register Bits
|
||||
static constexpr uint8_t DS248X_STATUS_BUSY = 0x01;
|
||||
static constexpr uint8_t DS248X_STATUS_PPD = 0x02;
|
||||
static constexpr uint8_t DS248X_STATUS_SD = 0x04;
|
||||
static constexpr uint8_t DS248X_STATUS_RST = 0x10;
|
||||
static constexpr uint8_t DS248X_STATUS_SBR = 0x20;
|
||||
static constexpr uint8_t DS248X_STATUS_TSB = 0x40;
|
||||
static constexpr uint8_t DS248X_STATUS_DIR = 0x80;
|
||||
|
||||
// DS248x Register Pointers
|
||||
static constexpr uint8_t DS248X_POINTER_STATUS = 0xF0;
|
||||
static constexpr uint8_t DS248X_POINTER_DATA = 0xE1;
|
||||
static constexpr uint8_t DS248X_POINTER_CONFIG = 0xC3;
|
||||
|
||||
// DS248x Configuration Bits
|
||||
static constexpr uint8_t DS248X_CONFIG_ACTIVE_PULLUP = 0x01;
|
||||
|
||||
/**
|
||||
* @brief DS248x I2C-to-1-Wire Bridge Component.
|
||||
*
|
||||
* This component manages the DS248x chip (DS2482-100, DS2482-800, DS2484).
|
||||
* It provides low-level 1-Wire bus operations via I2C.
|
||||
*
|
||||
* Usage: Configure DS248xOneWireBus instances for each channel.
|
||||
* These buses implement the one_wire::OneWireBus interface for compatibility
|
||||
* with all existing 1-Wire device components (dallas_temp, etc.).
|
||||
*/
|
||||
class DS248xComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_sleep_pin(InternalGPIOPin *pin) { this->sleep_pin_ = pin; }
|
||||
void set_bus_sleep(bool enabled) { this->bus_sleep_ = enabled; }
|
||||
void set_hub_sleep(bool enabled) { this->hub_sleep_ = enabled; }
|
||||
void set_channel_count(uint8_t count) { this->channel_count_ = count; }
|
||||
void set_active_pullup(bool enabled) { this->active_pullup_ = enabled; }
|
||||
|
||||
// DS2484 Timing Parameters
|
||||
void set_val_trstl(uint8_t val) {
|
||||
this->ds2484_trstl_ = val;
|
||||
this->ds2484_mode_ = true;
|
||||
}
|
||||
void set_val_tmsp(uint8_t val) {
|
||||
this->ds2484_tmsp_ = val;
|
||||
this->ds2484_mode_ = true;
|
||||
}
|
||||
void set_val_tw0l(uint8_t val) {
|
||||
this->ds2484_tw0l_ = val;
|
||||
this->ds2484_mode_ = true;
|
||||
}
|
||||
void set_val_trec0(uint8_t val) {
|
||||
this->ds2484_trec0_ = val;
|
||||
this->ds2484_mode_ = true;
|
||||
}
|
||||
void set_val_rwpu(uint8_t val) {
|
||||
this->ds2484_rwpu_ = val;
|
||||
this->ds2484_mode_ = true;
|
||||
}
|
||||
|
||||
/// Get the channel count (1 for DS2482-100/DS2484, 8 for DS2482-800)
|
||||
uint8_t get_channel_count() const { return this->channel_count_; }
|
||||
|
||||
// --- Core 1-Wire API (used by DS248xOneWireBus) ---
|
||||
bool select_channel(uint8_t channel);
|
||||
bool ow_reset(bool &presence);
|
||||
bool ow_write_byte(uint8_t byte);
|
||||
bool ow_read_byte(uint8_t &byte);
|
||||
|
||||
// --- Search support (used by DS248xOneWireBus) ---
|
||||
bool search_triplet(bool search_direction, uint8_t &status);
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *sleep_pin_{nullptr};
|
||||
uint8_t channel_count_ = 1;
|
||||
bool bus_sleep_{false};
|
||||
bool hub_sleep_{false};
|
||||
bool active_pullup_ = false;
|
||||
|
||||
// DS2484 Config
|
||||
bool ds2484_mode_ = false;
|
||||
static constexpr uint8_t DS2484_PARAM_UNSET = 0xFF;
|
||||
uint8_t ds2484_trstl_{DS2484_PARAM_UNSET};
|
||||
uint8_t ds2484_tmsp_{DS2484_PARAM_UNSET};
|
||||
uint8_t ds2484_tw0l_{DS2484_PARAM_UNSET};
|
||||
uint8_t ds2484_trec0_{DS2484_PARAM_UNSET};
|
||||
uint8_t ds2484_rwpu_{DS2484_PARAM_UNSET};
|
||||
|
||||
int8_t current_channel_{-1};
|
||||
|
||||
// Internal helpers
|
||||
bool set_read_pointer_(uint8_t ptr);
|
||||
bool wait_busy_();
|
||||
bool device_reset_();
|
||||
bool device_configure_();
|
||||
bool configure_ds2484_port_(uint8_t param, uint8_t val);
|
||||
bool write_config_();
|
||||
};
|
||||
|
||||
} // namespace esphome::ds248x
|
||||
@@ -0,0 +1,171 @@
|
||||
#include "ds248x_one_wire_bus.h"
|
||||
#include "ds248x.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::ds248x {
|
||||
|
||||
static const char *const TAG = "ds248x.one_wire";
|
||||
|
||||
void DS248xOneWireBus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up DS248x 1-Wire Bus (Channel %d)...", this->channel_);
|
||||
|
||||
// Parent setup happens in DS248xComponent::setup()
|
||||
// We just need to scan for devices on this channel
|
||||
if (!this->ensure_channel_()) {
|
||||
ESP_LOGE(TAG, "Failed to select channel %d during setup", this->channel_);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform device search on this channel
|
||||
this->search();
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Found %zu devices on channel %d", this->devices_.size(), this->channel_);
|
||||
}
|
||||
|
||||
void DS248xOneWireBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DS248x 1-Wire Bus (Channel %d):", this->channel_);
|
||||
this->dump_devices_(TAG);
|
||||
}
|
||||
|
||||
bool DS248xOneWireBus::ensure_channel_() {
|
||||
if (this->parent_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Parent not set!");
|
||||
return false;
|
||||
}
|
||||
return this->parent_->select_channel(this->channel_);
|
||||
}
|
||||
|
||||
int DS248xOneWireBus::reset_int() {
|
||||
if (!this->ensure_channel_()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool presence = false;
|
||||
if (!this->parent_->ow_reset(presence)) {
|
||||
return -1;
|
||||
}
|
||||
return presence ? 1 : 0;
|
||||
}
|
||||
|
||||
void DS248xOneWireBus::write8(uint8_t val) {
|
||||
if (!this->ensure_channel_()) {
|
||||
return;
|
||||
}
|
||||
if (!this->parent_->ow_write_byte(val)) {
|
||||
ESP_LOGE(TAG, "Failed to write byte 0x%02X on channel %d", val, this->channel_);
|
||||
}
|
||||
}
|
||||
|
||||
void DS248xOneWireBus::write64(uint64_t val) {
|
||||
if (!this->ensure_channel_()) {
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint8_t byte = static_cast<uint8_t>(val >> (i * 8));
|
||||
if (!this->parent_->ow_write_byte(byte)) {
|
||||
ESP_LOGE(TAG, "Failed to write byte %d/8 (0x%02X) on channel %d - aborting write64", i + 1, byte, this->channel_);
|
||||
return; // Stop writing to prevent sending corrupted data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t DS248xOneWireBus::read8() {
|
||||
if (!this->ensure_channel_()) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t value = 0;
|
||||
if (!this->parent_->ow_read_byte(value)) {
|
||||
ESP_LOGE(TAG, "Failed to read byte on channel %d", this->channel_);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint64_t DS248xOneWireBus::read64() {
|
||||
if (!this->ensure_channel_()) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t value = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint8_t byte = 0;
|
||||
if (!this->parent_->ow_read_byte(byte)) {
|
||||
ESP_LOGE(TAG, "Failed to read byte %d/8 on channel %d - returning partial data", i + 1, this->channel_);
|
||||
return value; // Return partial data to avoid blocking, caller should validate
|
||||
}
|
||||
value |= (static_cast<uint64_t>(byte) << (i * 8));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void DS248xOneWireBus::reset_search() {
|
||||
this->search_last_discrepancy_ = 0;
|
||||
this->search_last_device_flag_ = false;
|
||||
this->search_address_ = 0;
|
||||
}
|
||||
|
||||
uint64_t DS248xOneWireBus::search_int() {
|
||||
if (!this->ensure_channel_()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->search_last_device_flag_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t last_zero = 0;
|
||||
uint64_t address = this->search_address_;
|
||||
|
||||
// Iterate through all 64 bits
|
||||
for (uint8_t bit_number = 1; bit_number <= 64; bit_number++) {
|
||||
uint64_t bit_mask = 1ULL << (bit_number - 1);
|
||||
|
||||
// Determine search direction
|
||||
bool search_direction;
|
||||
if (bit_number < this->search_last_discrepancy_) {
|
||||
search_direction = (address & bit_mask) != 0;
|
||||
} else {
|
||||
search_direction = (bit_number == this->search_last_discrepancy_);
|
||||
}
|
||||
|
||||
// Perform triplet operation
|
||||
uint8_t status = 0;
|
||||
if (!this->parent_->search_triplet(search_direction, status)) {
|
||||
ESP_LOGW(TAG, "1-Wire triplet failed at bit %d on channel %d - aborting search", bit_number, this->channel_);
|
||||
this->reset_search();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool id_bit = (status & DS248X_STATUS_SBR) != 0;
|
||||
bool cmp_id_bit = (status & DS248X_STATUS_TSB) != 0;
|
||||
bool dir_taken = (status & DS248X_STATUS_DIR) != 0;
|
||||
|
||||
if (id_bit && cmp_id_bit) {
|
||||
// No devices participating
|
||||
this->reset_search();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!id_bit && !cmp_id_bit && !dir_taken) {
|
||||
// Discrepancy, went 0 - record position
|
||||
last_zero = bit_number;
|
||||
}
|
||||
|
||||
// Update address based on direction taken
|
||||
if (dir_taken) {
|
||||
address |= bit_mask;
|
||||
} else {
|
||||
address &= ~bit_mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Search successful
|
||||
this->search_last_discrepancy_ = last_zero;
|
||||
if (last_zero == 0) {
|
||||
this->search_last_device_flag_ = true;
|
||||
}
|
||||
this->search_address_ = address;
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
} // namespace esphome::ds248x
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/one_wire/one_wire_bus.h"
|
||||
|
||||
namespace esphome::ds248x {
|
||||
|
||||
class DS248xComponent;
|
||||
|
||||
/**
|
||||
* @brief OneWireBus implementation for DS248x I2C-to-1-Wire bridges.
|
||||
*
|
||||
* This class wraps the DS248xComponent to provide the one_wire::OneWireBus interface,
|
||||
* enabling compatibility with all existing 1-Wire device components (dallas_temp, etc.).
|
||||
*
|
||||
* For DS2482-800, multiple instances of this class can be created (one per channel).
|
||||
* For DS2482-100/DS2484, a single instance is used.
|
||||
*/
|
||||
class DS248xOneWireBus : public one_wire::OneWireBus, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
|
||||
|
||||
/// Set the parent DS248x component
|
||||
void set_parent(DS248xComponent *parent) { this->parent_ = parent; }
|
||||
|
||||
/// Set the 1-Wire channel (0-7, only relevant for DS2482-800)
|
||||
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||
|
||||
/// Get the channel number
|
||||
uint8_t get_channel() const { return this->channel_; }
|
||||
|
||||
// OneWireBus interface implementation
|
||||
int reset_int() override;
|
||||
void write8(uint8_t val) override;
|
||||
void write64(uint64_t val) override;
|
||||
uint8_t read8() override;
|
||||
uint64_t read64() override;
|
||||
|
||||
protected:
|
||||
void reset_search() override;
|
||||
uint64_t search_int() override;
|
||||
|
||||
/// Select the channel on the DS248x before any 1-Wire operation
|
||||
bool ensure_channel_();
|
||||
|
||||
DS248xComponent *parent_{nullptr};
|
||||
uint8_t channel_{0};
|
||||
|
||||
// Search state
|
||||
uint64_t search_address_{0};
|
||||
uint8_t search_last_discrepancy_{0};
|
||||
bool search_last_device_flag_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::ds248x
|
||||
@@ -0,0 +1,56 @@
|
||||
"""DS248x 1-Wire Bus Platform.
|
||||
|
||||
This platform creates one_wire bus instances backed by a DS248x I2C-to-1-Wire bridge.
|
||||
It supports DS2482-100/101 (single channel), DS2482-800 (8 channels), and DS2484 (single channel).
|
||||
|
||||
For multi-channel devices (DS2482-800), create one platform entry per channel.
|
||||
Each entry becomes a separate one_wire bus that can be used by dallas_temp and other 1-Wire devices.
|
||||
"""
|
||||
|
||||
from esphome import final_validate as fv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.one_wire import OneWireBus
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
|
||||
from . import CONF_DS248X_ID, DS248xComponent, ds248x_ns, get_channel_count
|
||||
|
||||
CODEOWNERS = ["@tomwellnitz"]
|
||||
DEPENDENCIES = ["ds248x"]
|
||||
|
||||
DS248xOneWireBus = ds248x_ns.class_("DS248xOneWireBus", OneWireBus, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DS248xOneWireBus),
|
||||
cv.GenerateID(CONF_DS248X_ID): cv.use_id(DS248xComponent),
|
||||
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=7),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
"""Validate that the channel is within the parent's channel count."""
|
||||
fconf = fv.full_config.get()
|
||||
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
|
||||
parent_config = fconf.get_config_for_path(path)
|
||||
channel_count = get_channel_count(parent_config)
|
||||
channel = config[CONF_CHANNEL]
|
||||
|
||||
if channel >= channel_count:
|
||||
raise cv.Invalid(
|
||||
f"Channel {channel} is invalid for DS248x with {channel_count} channel(s). "
|
||||
f"Valid range: 0-{channel_count - 1}"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_DS248X_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
@@ -145,9 +145,9 @@ float Emc2101Component::get_external_temperature() {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
// join msb and lsb (5 least significant bits are not used)
|
||||
uint16_t raw = (msb << 8 | lsb) >> 5;
|
||||
return raw * 0.125;
|
||||
// join msb and lsb (5 least significant bits are not used); msb is signed, so read as int16_t
|
||||
int16_t raw = static_cast<int16_t>((msb << 8) | lsb) >> 5;
|
||||
return raw * 0.125f;
|
||||
}
|
||||
|
||||
float Emc2101Component::get_speed() {
|
||||
|
||||
@@ -14,10 +14,9 @@ void EPaperMono::refresh_screen(bool partial) {
|
||||
}
|
||||
|
||||
void EPaperMono::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
if (this->is_using_partial_update_()) {
|
||||
this->cmd_data(0x10, {0x00}); // sleep in power on mode
|
||||
} else {
|
||||
// Deep sleep loses RAM so cannot be used with partial update
|
||||
if (!this->is_using_partial_update_()) {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->cmd_data(0x10, {0x03}); // deep sleep
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3058,19 +3058,23 @@ def copy_files():
|
||||
|
||||
|
||||
def _decode_pc(config, addr):
|
||||
# _decode_pc runs from the api log processor's asyncio callback, which
|
||||
# only catches EsphomeError. Any other exception escaping here tears down
|
||||
# the protocol and triggers an infinite reconnect/replay loop. Convert
|
||||
# toolchain-resolution errors (e.g. missing build dir / cmake cache) into
|
||||
# EsphomeError so the caller can disable decoding cleanly.
|
||||
# Convert toolchain-resolution errors (e.g. missing build dir / cmake
|
||||
# cache) into EsphomeError. The api log processor stops decoding on any
|
||||
# exception, so this is about the message it reports rather than about
|
||||
# catching it at all: EsphomeError carries an explanation worth showing
|
||||
# the user, where a raw OSError repr does not.
|
||||
if CORE.using_toolchain_esp_idf:
|
||||
from esphome.espidf import toolchain as idf_toolchain
|
||||
|
||||
try:
|
||||
addr2line_path = idf_toolchain.get_addr2line_path()
|
||||
firmware_elf_path = idf_toolchain.get_elf_path()
|
||||
except RuntimeError as err:
|
||||
except (RuntimeError, OSError) as err:
|
||||
# OSError covers a missing build directory or a cmake that isn't
|
||||
# on PATH; both surface from the subprocess call, not as RuntimeError.
|
||||
raise EsphomeError(f"ESP-IDF toolchain not available: {err}") from err
|
||||
if not firmware_elf_path.is_file():
|
||||
raise EsphomeError(f"Firmware ELF not found: {firmware_elf_path}")
|
||||
else:
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
|
||||
@@ -181,12 +181,6 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
|
||||
return output.data();
|
||||
}
|
||||
}
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
char buf[UUID_STR_LEN];
|
||||
this->to_str(buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
#endif // USE_ESP32_BLE_UUID
|
||||
|
||||
@@ -46,9 +46,6 @@ class ESPBTUUID {
|
||||
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const; // NOLINT
|
||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -77,6 +77,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTS_WRITE_EVT: {
|
||||
if (this->handle_ != param->write.handle)
|
||||
break;
|
||||
if (param->write.len > this->value_.attr_max_len) {
|
||||
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", param->write.len, this->value_.attr_max_len);
|
||||
break;
|
||||
}
|
||||
this->value_.attr_len = param->write.len;
|
||||
memcpy(this->value_.attr_value, param->write.value, param->write.len);
|
||||
if (this->on_write_callback_) {
|
||||
|
||||
@@ -112,8 +112,10 @@ enum class EthernetComponentState : uint8_t {
|
||||
|
||||
// Platform-neutral duplex/speed types
|
||||
#ifndef USE_ESP32
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
enum eth_duplex_t { ETH_DUPLEX_HALF, ETH_DUPLEX_FULL };
|
||||
enum eth_speed_t { ETH_SPEED_10M, ETH_SPEED_100M };
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
#endif
|
||||
|
||||
class EthernetComponent final : public Component {
|
||||
|
||||
@@ -187,17 +187,18 @@ void EthernetComponent::loop() {
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
const char *type_str = "Unknown";
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
type_str = "W5500";
|
||||
const char *type_str = "W5500";
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
type_str = "W5100";
|
||||
const char *type_str = "W5100";
|
||||
#elif defined(USE_ETHERNET_W6100)
|
||||
type_str = "W6100";
|
||||
const char *type_str = "W6100";
|
||||
#elif defined(USE_ETHERNET_W6300)
|
||||
type_str = "W6300";
|
||||
const char *type_str = "W6300";
|
||||
#elif defined(USE_ETHERNET_ENC28J60)
|
||||
type_str = "ENC28J60";
|
||||
const char *type_str = "ENC28J60";
|
||||
#else
|
||||
const char *type_str = "Unknown";
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_W6300)
|
||||
// W6300 uses PIO QSPI with hardcoded pins — SPI pin fields are not used
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "fastled_light.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -132,7 +132,7 @@ void HaierClimateBase::save_settings() {
|
||||
}
|
||||
|
||||
bool HaierClimateBase::get_display_state() const {
|
||||
return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON);
|
||||
return (this->display_status_ == SwitchState::SWITCH_ON) || (this->display_status_ == SwitchState::PENDING_ON);
|
||||
}
|
||||
|
||||
void HaierClimateBase::set_display_state(bool state) {
|
||||
@@ -144,7 +144,7 @@ void HaierClimateBase::set_display_state(bool state) {
|
||||
}
|
||||
|
||||
bool HaierClimateBase::get_health_mode() const {
|
||||
return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
|
||||
return (this->health_mode_ == SwitchState::SWITCH_ON) || (this->health_mode_ == SwitchState::PENDING_ON);
|
||||
}
|
||||
|
||||
void HaierClimateBase::set_health_mode(bool state) {
|
||||
|
||||
@@ -147,8 +147,8 @@ class HaierClimateBase : public esphome::Component,
|
||||
esphome::optional<haier_protocol::HaierMessage> message;
|
||||
};
|
||||
enum class SwitchState {
|
||||
OFF = 0b00,
|
||||
ON = 0b01,
|
||||
SWITCH_OFF = 0b00,
|
||||
SWITCH_ON = 0b01,
|
||||
PENDING_OFF = 0b10,
|
||||
PENDING_ON = 0b11,
|
||||
};
|
||||
@@ -157,8 +157,8 @@ class HaierClimateBase : public esphome::Component,
|
||||
esphome::optional<PendingAction> action_request_;
|
||||
uint8_t fan_mode_speed_;
|
||||
uint8_t other_modes_fan_speed_;
|
||||
SwitchState display_status_{SwitchState::ON};
|
||||
SwitchState health_mode_{SwitchState::OFF};
|
||||
SwitchState display_status_{SwitchState::SWITCH_ON};
|
||||
SwitchState health_mode_{SwitchState::SWITCH_OFF};
|
||||
bool force_send_control_;
|
||||
bool forced_request_status_;
|
||||
bool reset_protocol_request_;
|
||||
|
||||
@@ -50,7 +50,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
|
||||
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||
this->force_send_control_ = true;
|
||||
} else {
|
||||
this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
|
||||
this->quiet_mode_state_ = state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
}
|
||||
this->settings_.quiet_mode_state = state;
|
||||
#ifdef USE_SWITCH
|
||||
@@ -63,7 +63,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
|
||||
}
|
||||
|
||||
bool HonClimate::get_quiet_mode_state() const {
|
||||
return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
|
||||
return (this->quiet_mode_state_ == SwitchState::SWITCH_ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
|
||||
}
|
||||
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
|
||||
@@ -513,7 +513,7 @@ void HonClimate::initialization() {
|
||||
}
|
||||
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
|
||||
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
|
||||
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
|
||||
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
}
|
||||
|
||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
@@ -825,7 +825,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
#ifdef USE_SENSOR
|
||||
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_defrost_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
|
||||
@@ -939,14 +939,14 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
// AC just turned on from remote need to turn off display
|
||||
this->force_send_control_ = true;
|
||||
} else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
|
||||
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
|
||||
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Health mode
|
||||
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||
bool old_health_mode = this->get_health_mode();
|
||||
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
|
||||
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
should_publish = should_publish || (old_health_mode != this->get_health_mode());
|
||||
}
|
||||
{
|
||||
@@ -1008,7 +1008,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
// In proper mode and not in pending state
|
||||
bool new_quiet_mode = packet.control.quiet_mode != 0;
|
||||
if (new_quiet_mode != this->get_quiet_mode_state()) {
|
||||
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
|
||||
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
this->settings_.quiet_mode_state = new_quiet_mode;
|
||||
#ifdef USE_SWITCH
|
||||
if (this->quiet_mode_switch_ != nullptr) {
|
||||
|
||||
@@ -197,7 +197,7 @@ class HonClimate final : public HaierClimateBase {
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
||||
HonSettings settings_{};
|
||||
ESPPreferenceObject hon_rtc_;
|
||||
SwitchState quiet_mode_state_{SwitchState::OFF};
|
||||
SwitchState quiet_mode_state_{SwitchState::SWITCH_OFF};
|
||||
};
|
||||
|
||||
} // namespace esphome::haier
|
||||
|
||||
@@ -464,14 +464,14 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
||||
// AC just turned on from remote need to turn off display
|
||||
this->force_send_control_ = true;
|
||||
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
|
||||
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Health mode
|
||||
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||
bool old_health_mode = this->get_health_mode();
|
||||
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
|
||||
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
|
||||
should_publish = should_publish || (old_health_mode != this->get_health_mode());
|
||||
}
|
||||
{
|
||||
|
||||
@@ -242,7 +242,7 @@ void HlkFm22xComponent::handle_reply_(const uint8_t *data, size_t length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[1] != HlkFm22xResult::SUCCESS) {
|
||||
if (data[1] != HlkFm22xResult::SUCCEEDED) {
|
||||
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
|
||||
switch (expected) {
|
||||
case HlkFm22xCommand::ENROLL:
|
||||
|
||||
@@ -41,7 +41,7 @@ enum HlkFm22xNoteType {
|
||||
};
|
||||
|
||||
enum HlkFm22xResult {
|
||||
SUCCESS = 0x00,
|
||||
SUCCEEDED = 0x00,
|
||||
REJECTED = 0x01,
|
||||
ABORTED = 0x02,
|
||||
FAILED4_CAMERA = 0x04,
|
||||
|
||||
@@ -61,7 +61,7 @@ void HM3301Component::update() {
|
||||
int16_t aqi_value = -1;
|
||||
if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
|
||||
aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
|
||||
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
|
||||
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value, /*extended_range=*/false);
|
||||
}
|
||||
|
||||
if (pm_1_0_value != -1) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "http_request_arduino.h"
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/watchdog/watchdog.h"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "http_request.h"
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
|
||||
|
||||
#if defined(USE_RP2)
|
||||
#include <HTTPClient.h>
|
||||
|
||||
@@ -19,11 +19,6 @@ namespace esphome::http_request {
|
||||
static const char *const TAG = "http_request.idf";
|
||||
static constexpr uint32_t ERROR_DURATION_MS = 1000;
|
||||
|
||||
struct UserData {
|
||||
const std::vector<std::string> &lower_case_collect_headers;
|
||||
std::vector<Header> &response_headers;
|
||||
};
|
||||
|
||||
void HttpRequestIDF::dump_config() {
|
||||
HttpRequestComponent::dump_config();
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -34,15 +29,15 @@ void HttpRequestIDF::dump_config() {
|
||||
}
|
||||
|
||||
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
UserData *user_data = (UserData *) evt->user_data;
|
||||
auto *container = (HttpContainerIDF *) evt->user_data;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_HEADER: {
|
||||
const std::string header_name = str_lower_case(evt->header_key); // NOLINT
|
||||
if (should_collect_header(user_data->lower_case_collect_headers, header_name)) {
|
||||
if (should_collect_header(container->collect_headers_, header_name)) {
|
||||
const std::string header_value = evt->header_value;
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
user_data->response_headers.push_back({header_name, header_value});
|
||||
container->response_headers_.push_back({header_name, header_value});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -124,8 +119,8 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
|
||||
container->set_secure(secure);
|
||||
|
||||
auto user_data = UserData{lower_case_collect_headers, container->response_headers_};
|
||||
esp_http_client_set_user_data(client, static_cast<void *>(&user_data));
|
||||
container->collect_headers_ = lower_case_collect_headers;
|
||||
esp_http_client_set_user_data(client, static_cast<void *>(container.get()));
|
||||
|
||||
for (const auto &header : request_headers) {
|
||||
esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
|
||||
|
||||
@@ -24,6 +24,8 @@ class HttpContainerIDF : public HttpContainer {
|
||||
protected:
|
||||
friend class HttpRequestIDF;
|
||||
esp_http_client_handle_t client_;
|
||||
// Owned copy (not a reference): must outlive perform() for the response-header event handler
|
||||
std::vector<std::string> collect_headers_;
|
||||
};
|
||||
|
||||
class HttpRequestIDF final : public HttpRequestComponent {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "i2c_bus_arduino.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include <Wire.h>
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
@@ -42,4 +42,4 @@ async def setup_improv_core(var: MockObj, config: ConfigType, component: str):
|
||||
cg.add(var.set_next_url(_process_next_url(next_url)))
|
||||
cg.add_define(f"USE_{component.upper()}_NEXT_URL")
|
||||
|
||||
cg.add_library("improv/Improv", "1.2.4")
|
||||
cg.add_library("improv/Improv", "1.2.6")
|
||||
|
||||
@@ -44,7 +44,7 @@ void arch_init() {
|
||||
|
||||
void arch_restart() {
|
||||
lt_reboot();
|
||||
while (1) {
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
// it is callable from Thumb code via interworking. The MRS CPSR instruction
|
||||
// is ARM-only and user code here may be built in Thumb, so in_isr_context()
|
||||
// defers to this port helper on BK72xx instead of reading CPSR inline.
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
extern "C" uint32_t platform_is_in_interrupt_context(void);
|
||||
#endif
|
||||
|
||||
@@ -59,9 +60,11 @@ extern "C" void delayMicroseconds(unsigned int us);
|
||||
// Forward decls from libretiny's <lt_api.h> family for the inline arch_*
|
||||
// wrappers below. Pulling the full header would drag in the rest of the
|
||||
// LibreTiny C API.
|
||||
// NOLINTBEGIN(readability-redundant-declaration)
|
||||
extern "C" void lt_wdt_feed(void);
|
||||
extern "C" uint32_t lt_cpu_get_cycle_count(void);
|
||||
extern "C" uint32_t lt_cpu_get_freq(void);
|
||||
// NOLINTEND(readability-redundant-declaration)
|
||||
|
||||
namespace esphome::libretiny {}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ void LTComponent::dump_config() {
|
||||
"LibreTiny:\n"
|
||||
" Version: %s\n"
|
||||
" Loglevel: %u",
|
||||
LT_BANNER_STR + 10, LT_LOGLEVEL);
|
||||
<_BANNER_STR[10], LT_LOGLEVEL);
|
||||
#if defined(__OPTIMIZE_SIZE__) && __OPTIMIZE_LEVEL__ > 0 && __OPTIMIZE_LEVEL__ <= 3
|
||||
ESP_LOGCONFIG(TAG, " Optimization: -Os, SDK: -O" STRINGIFY_MACRO(__OPTIMIZE_LEVEL__));
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_ != nullptr) {
|
||||
this->version_->publish_state(LT_BANNER_STR + 10);
|
||||
this->version_->publish_state(<_BANNER_STR[10]);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
#include <cstdint>
|
||||
|
||||
// Forward declare FlashDB types to avoid pulling in flashdb.h
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
struct fdb_kvdb;
|
||||
struct fdb_blob;
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ USB_SERIAL_JTAG = "USB_SERIAL_JTAG"
|
||||
USB_CDC = "USB_CDC"
|
||||
DEFAULT = "DEFAULT"
|
||||
|
||||
CONF_BOOT_LOG_BUFFER_SIZE = "boot_log_buffer_size"
|
||||
CONF_INITIAL_LEVEL = "initial_level"
|
||||
CONF_LOGGER_ID = "logger_id"
|
||||
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
|
||||
@@ -240,6 +241,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.validate_bytes, cv.int_range(min=160, max=65535)
|
||||
),
|
||||
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BOOT_LOG_BUFFER_SIZE, default=0): cv.All(
|
||||
cv.validate_bytes,
|
||||
cv.Any(
|
||||
cv.int_(0), # Disabled
|
||||
cv.int_range(min=512, max=16384),
|
||||
),
|
||||
),
|
||||
cv.SplitDefault(
|
||||
CONF_TASK_LOG_BUFFER_SIZE,
|
||||
esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names)
|
||||
@@ -350,6 +358,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
if task_log_buffer_size > 0:
|
||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||
cg.add_define("ESPHOME_TASK_LOG_BUFFER_SIZE", task_log_buffer_size)
|
||||
# Boot log buffer captures early WARN/ERROR lines for later replay
|
||||
# (logger.replay_boot_logs action). Buffer is a direct member of Logger.
|
||||
if (boot_log_buffer_size := config[CONF_BOOT_LOG_BUFFER_SIZE]) > 0:
|
||||
cg.add_define("USE_LOGGER_BOOT_LOG_BUFFER")
|
||||
cg.add_define("ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE", boot_log_buffer_size)
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
baud_rate,
|
||||
@@ -593,6 +606,27 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"logger.replay_boot_logs",
|
||||
LambdaAction,
|
||||
automation.maybe_conf(
|
||||
CONF_LOGGER_ID,
|
||||
{
|
||||
cv.GenerateID(CONF_LOGGER_ID): cv.use_id(Logger),
|
||||
},
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def logger_replay_boot_logs_to_code(config, action_id, template_arg, args):
|
||||
logger = await cg.get_variable(config[CONF_LOGGER_ID])
|
||||
text = str(cg.statement(logger.replay_boot_logs()))
|
||||
|
||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||
return automation.new_lambda_pvariable(
|
||||
action_id, lambda_, StatelessLambdaAction, template_arg
|
||||
)
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"logger_esp32.cpp": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "logger.h"
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -202,6 +203,61 @@ void Logger::process_messages_() {
|
||||
}
|
||||
|
||||
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
// Record layout in boot_log_buffer_: u16 text_len | u8 level | u32 millis | text
|
||||
static constexpr uint16_t BOOT_LOG_RECORD_HEADER_SIZE = 7;
|
||||
|
||||
void Logger::boot_log_capture_(uint8_t level, const LogBuffer &buf) {
|
||||
if (this->boot_log_done_ || level > ESPHOME_LOG_LEVEL_WARN || level == ESPHOME_LOG_LEVEL_NONE)
|
||||
return;
|
||||
// Strip the leading ANSI color prefix and trailing reset; the replay re-emits
|
||||
// the text through the normal formatter, which colors it again.
|
||||
const char *text = buf.data;
|
||||
uint16_t len = buf.pos;
|
||||
if (len > 0 && text[0] == '\033') {
|
||||
const char *m = static_cast<const char *>(memchr(text, 'm', len));
|
||||
if (m != nullptr) {
|
||||
const uint16_t skip = static_cast<uint16_t>(m - text) + 1;
|
||||
text += skip;
|
||||
len -= skip;
|
||||
}
|
||||
}
|
||||
if (len >= 4 && memcmp(text + len - 4, "\033[0m", 4) == 0)
|
||||
len -= 4;
|
||||
if (this->boot_log_pos_ + BOOT_LOG_RECORD_HEADER_SIZE + len > ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE) {
|
||||
this->boot_log_dropped_++;
|
||||
return;
|
||||
}
|
||||
char *p = this->boot_log_buffer_ + this->boot_log_pos_;
|
||||
const uint32_t now = millis();
|
||||
memcpy(p, &len, sizeof(len));
|
||||
p[2] = static_cast<char>(level);
|
||||
memcpy(p + 3, &now, sizeof(now));
|
||||
memcpy(p + BOOT_LOG_RECORD_HEADER_SIZE, text, len);
|
||||
this->boot_log_pos_ += BOOT_LOG_RECORD_HEADER_SIZE + len;
|
||||
}
|
||||
|
||||
void Logger::replay_boot_logs() {
|
||||
// Stop capturing first so the replay output is not buffered again.
|
||||
this->boot_log_done_ = true;
|
||||
ESP_LOGI(TAG, "Replaying boot logs (%u bytes used, %u lines dropped)", this->boot_log_pos_, this->boot_log_dropped_);
|
||||
uint16_t pos = 0;
|
||||
while (pos + BOOT_LOG_RECORD_HEADER_SIZE <= this->boot_log_pos_) {
|
||||
const char *p = this->boot_log_buffer_ + pos;
|
||||
uint16_t len;
|
||||
memcpy(&len, p, sizeof(len));
|
||||
uint32_t stamp;
|
||||
memcpy(&stamp, p + 3, sizeof(stamp));
|
||||
esp_log_printf_(static_cast<uint8_t>(p[2]), TAG, __LINE__, ESPHOME_LOG_FORMAT("@%" PRIu32 "ms %.*s"), stamp,
|
||||
static_cast<int>(len), p + BOOT_LOG_RECORD_HEADER_SIZE);
|
||||
pos += BOOT_LOG_RECORD_HEADER_SIZE + len;
|
||||
}
|
||||
ESP_LOGI(TAG, "Boot log replay done");
|
||||
}
|
||||
#else
|
||||
void Logger::replay_boot_logs() { ESP_LOGW(TAG, "Boot log buffer not configured (set 'boot_log_buffer_size')"); }
|
||||
#endif // USE_LOGGER_BOOT_LOG_BUFFER
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
|
||||
#endif
|
||||
@@ -239,6 +295,9 @@ void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_.size()));
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
ESP_LOGCONFIG(TAG, " Boot Log Buffer Size: %u bytes", ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
for (auto &it : this->log_levels_) {
|
||||
|
||||
@@ -174,6 +174,12 @@ class Logger final : public Component {
|
||||
#endif
|
||||
uint8_t get_log_level() { return this->current_level_; }
|
||||
|
||||
/// Re-emit WARN/ERROR messages captured during boot through the normal log
|
||||
/// dispatch so connected listeners (API, MQTT, ...) can see them. Stops
|
||||
/// further capture; safe to call multiple times. Warns when the logger was
|
||||
/// built without 'boot_log_buffer_size'.
|
||||
void replay_boot_logs();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Set up this component.
|
||||
@@ -291,6 +297,9 @@ class Logger final : public Component {
|
||||
{
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
|
||||
}
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
this->boot_log_capture_(level, buf);
|
||||
#endif
|
||||
this->notify_listeners_(level, tag, buf);
|
||||
this->write_log_buffer_to_console_(buf);
|
||||
}
|
||||
@@ -303,6 +312,9 @@ class Logger final : public Component {
|
||||
LogBuffer &buf) {
|
||||
buf.write_header(level, tag, line, thread_name);
|
||||
buf.write_body(text, text_length);
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
this->boot_log_capture_(level, buf);
|
||||
#endif
|
||||
this->notify_listeners_(level, tag, buf);
|
||||
}
|
||||
#endif
|
||||
@@ -311,6 +323,11 @@ class Logger final : public Component {
|
||||
const LogString *get_uart_selection_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
/// Copy a formatted WARN/ERROR line (ANSI codes stripped) into the boot log buffer.
|
||||
void boot_log_capture_(uint8_t level, const LogBuffer &buf);
|
||||
#endif
|
||||
|
||||
// Group 4-byte aligned members first
|
||||
uint32_t baud_rate_;
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
@@ -350,6 +367,11 @@ class Logger final : public Component {
|
||||
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
||||
#endif
|
||||
// Group smaller types together at the end
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
uint16_t boot_log_pos_{0}; // Bytes used in boot_log_buffer_
|
||||
uint16_t boot_log_dropped_{0}; // Lines that did not fit
|
||||
bool boot_log_done_{false}; // Set on first replay; stops further capture
|
||||
#endif
|
||||
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
@@ -368,6 +390,10 @@ class Logger final : public Component {
|
||||
|
||||
// Large buffers placed last to keep frequently-accessed member offsets small
|
||||
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
|
||||
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
|
||||
// Sequential records of early WARN/ERROR lines: u16 text_len | u8 level | u32 millis | text
|
||||
char boot_log_buffer_[ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE];
|
||||
#endif
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
logger::TaskLogBuffer log_buffer_; // Embedded in Logger (no separate heap allocation)
|
||||
#endif
|
||||
|
||||
@@ -20,7 +20,7 @@ TaskLogBuffer::~TaskLogBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
size_t TaskLogBuffer::available_contiguous_space() const {
|
||||
size_t TaskLogBuffer::available_contiguous_space_() const {
|
||||
if (this->head_ >= this->tail_) {
|
||||
// head is ahead of or equal to tail
|
||||
// Available space is from head to end, plus from start to tail
|
||||
@@ -81,7 +81,7 @@ void TaskLogBuffer::release_message_main_loop() {
|
||||
this->tail_ = 0;
|
||||
}
|
||||
|
||||
this->message_count_--;
|
||||
this->message_count_ = this->message_count_ - 1;
|
||||
this->current_message_size_ = 0;
|
||||
|
||||
xSemaphoreGive(this->mutex_);
|
||||
@@ -117,7 +117,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
}
|
||||
|
||||
// Check if we have enough contiguous space
|
||||
size_t contiguous = this->available_contiguous_space();
|
||||
size_t contiguous = this->available_contiguous_space_();
|
||||
|
||||
if (contiguous < total_size) {
|
||||
// Not enough contiguous space at end
|
||||
@@ -128,9 +128,9 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
}
|
||||
|
||||
// Need at least enough space to safely write padding marker (level field is at end of struct)
|
||||
constexpr size_t PADDING_MARKER_MIN_SPACE = offsetof(LogMessage, level) + 1;
|
||||
constexpr size_t padding_marker_min_space = offsetof(LogMessage, level) + 1;
|
||||
|
||||
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= PADDING_MARKER_MIN_SPACE) {
|
||||
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= padding_marker_min_space) {
|
||||
// Add padding marker (set level field to indicate this is padding, not a real message)
|
||||
LogMessage *padding = reinterpret_cast<LogMessage *>(this->storage_ + this->head_);
|
||||
padding->level = PADDING_MARKER_LEVEL;
|
||||
@@ -180,7 +180,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
||||
this->head_ = 0;
|
||||
}
|
||||
|
||||
this->message_count_++;
|
||||
this->message_count_ = this->message_count_ + 1;
|
||||
|
||||
xSemaphoreGive(this->mutex_);
|
||||
return true;
|
||||
|
||||
@@ -84,7 +84,7 @@ class TaskLogBuffer {
|
||||
static inline size_t message_total_size(size_t text_length) { return sizeof(LogMessage) + text_length + 1; }
|
||||
|
||||
// Calculate available contiguous space at write position
|
||||
size_t available_contiguous_space() const;
|
||||
size_t available_contiguous_space_() const;
|
||||
|
||||
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
|
||||
size_t head_{0}; // Write position
|
||||
|
||||
@@ -27,8 +27,8 @@ static void register_libretiny(MDNSComponent *, StaticVector<MDNSService, MDNS_S
|
||||
while (*service_type == '_') {
|
||||
service_type++;
|
||||
}
|
||||
uint16_t port_ = service.port.value();
|
||||
MDNS.addService(service_type, proto, port_);
|
||||
uint16_t port = service.port.value();
|
||||
MDNS.addService(service_type, proto, port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ from urllib.parse import urljoin
|
||||
|
||||
from esphome import automation, external_files, git
|
||||
from esphome.automation import register_action, register_condition
|
||||
from esphome.bundle import add_bundle_file
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, microphone, ota, psram
|
||||
import esphome.config_validation as cv
|
||||
@@ -28,6 +29,7 @@ from esphome.const import (
|
||||
TYPE_LOCAL,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -236,10 +238,45 @@ HTTP_SCHEMA = cv.All(
|
||||
_process_http_source,
|
||||
)
|
||||
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_),
|
||||
}
|
||||
|
||||
def _register_local_model_file(config: ConfigType) -> ConfigType:
|
||||
"""Register the model file that the manifest points to, so bundles include it.
|
||||
|
||||
The manifest names its model file relative to itself, so that path never appears
|
||||
in the YAML and bundle discovery cannot find it on its own.
|
||||
|
||||
Problems with the manifest are logged and ignored here rather than raised. Loading
|
||||
the manifest later reports them with better messages, and raising would be
|
||||
swallowed by the shorthand validator, which then reports a confusing error about a
|
||||
missing file in a git repository. Logging keeps the skipped registration
|
||||
diagnosable if the manifest is only briefly unreadable, since the bundle would
|
||||
then be built without the model file.
|
||||
"""
|
||||
manifest_path: Path = config[CONF_PATH]
|
||||
try:
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
model = manifest[CONF_MODEL]
|
||||
except (OSError, ValueError, KeyError, TypeError) as err:
|
||||
_LOGGER.debug("Not registering a model file from %s: %s", manifest_path, err)
|
||||
return config
|
||||
if not isinstance(model, str):
|
||||
_LOGGER.debug(
|
||||
"Not registering a model file from %s: 'model' is %s, expected a string",
|
||||
manifest_path,
|
||||
type(model).__name__,
|
||||
)
|
||||
return config
|
||||
add_bundle_file(manifest_path.parent / model)
|
||||
return config
|
||||
|
||||
|
||||
LOCAL_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_),
|
||||
}
|
||||
),
|
||||
_register_local_model_file,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "ac_adapter.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/AirConditioner/AirConditioner.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "air_conditioner.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/AirConditioner/AirConditioner.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/ApplianceBase.h>
|
||||
|
||||
@@ -25,6 +25,8 @@ from esphome.const import (
|
||||
ICON_POWER,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -152,6 +154,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
# Actions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
#include "esphome/components/remote_base/midea_protocol.h"
|
||||
|
||||
|
||||
@@ -318,4 +318,232 @@ DsiDriverChip(
|
||||
(0xE0, 0x00),
|
||||
]
|
||||
)
|
||||
|
||||
# JC8012P4A1 V2 Driver Configuration (jd9365)
|
||||
# Some units of this model have a different LCD panel but still use the same JD9365 driver chip.
|
||||
# Using parameters from esp_lcd_jd9365.h and the working full init sequence
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# * Resolution: 800x1280
|
||||
# * PCLK Frequency: 70 MHz
|
||||
# * DSI Lane Bit Rate: 1.5 Gbps (using 2-Lane DSI configuration)
|
||||
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
|
||||
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=10, vsync_front_porch=20)
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
DsiDriverChip(
|
||||
"JC8012P4A1-V2",
|
||||
width=800,
|
||||
height=1280,
|
||||
hsync_back_porch=20,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=10,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="70MHz",
|
||||
lane_bit_rate="1500Mbps",
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(0xE0, 0x00),
|
||||
(0xE1, 0x93),
|
||||
(0xE2, 0x65),
|
||||
(0xE3, 0xF8),
|
||||
(0x80, 0x01),
|
||||
(0xE0, 0x01),
|
||||
(0x00, 0x00),
|
||||
(0x01, 0x44),
|
||||
(0x03, 0x10),
|
||||
(0x04, 0x38),
|
||||
(0x0C, 0x74),
|
||||
(0x17, 0x00),
|
||||
(0x18, 0xAF),
|
||||
(0x19, 0x00),
|
||||
(0x1A, 0x00),
|
||||
(0x1B, 0xAF),
|
||||
(0x1C, 0x00),
|
||||
(0x35, 0x26),
|
||||
(0x37, 0x09),
|
||||
(0x38, 0x04),
|
||||
(0x39, 0x00),
|
||||
(0x3A, 0x01),
|
||||
(0x3C, 0x78),
|
||||
(0x3D, 0xFF),
|
||||
(0x3E, 0xFF),
|
||||
(0x3F, 0x7F),
|
||||
(0x40, 0x06),
|
||||
(0x41, 0xA0),
|
||||
(0x42, 0x81),
|
||||
(0x43, 0x1E),
|
||||
(0x44, 0x0D),
|
||||
(0x45, 0x28),
|
||||
(0x55, 0x02),
|
||||
(0x57, 0x69),
|
||||
(0x59, 0x0A),
|
||||
(0x5A, 0x2A),
|
||||
(0x5B, 0x17),
|
||||
(0x5D, 0x7F),
|
||||
(0x5E, 0x6B),
|
||||
(0x5F, 0x5C),
|
||||
(0x60, 0x50),
|
||||
(0x61, 0x4C),
|
||||
(0x62, 0x3E),
|
||||
(0x63, 0x41),
|
||||
(0x64, 0x2B),
|
||||
(0x65, 0x43),
|
||||
(0x66, 0x42),
|
||||
(0x67, 0x43),
|
||||
(0x68, 0x62),
|
||||
(0x69, 0x52),
|
||||
(0x6A, 0x5A),
|
||||
(0x6B, 0x4C),
|
||||
(0x6C, 0x48),
|
||||
(0x6D, 0x3A),
|
||||
(0x6E, 0x28),
|
||||
(0x6F, 0x10),
|
||||
(0x70, 0x7F),
|
||||
(0x71, 0x6B),
|
||||
(0x72, 0x5C),
|
||||
(0x73, 0x50),
|
||||
(0x74, 0x4C),
|
||||
(0x75, 0x3E),
|
||||
(0x76, 0x41),
|
||||
(0x77, 0x2B),
|
||||
(0x78, 0x43),
|
||||
(0x79, 0x42),
|
||||
(0x7A, 0x43),
|
||||
(0x7B, 0x62),
|
||||
(0x7C, 0x52),
|
||||
(0x7D, 0x5A),
|
||||
(0x7E, 0x4C),
|
||||
(0x7F, 0x48),
|
||||
(0x80, 0x3A),
|
||||
(0x81, 0x28),
|
||||
(0x82, 0x10),
|
||||
(0xE0, 0x02),
|
||||
(0x00, 0x42),
|
||||
(0x01, 0x42),
|
||||
(0x02, 0x40),
|
||||
(0x03, 0x40),
|
||||
(0x04, 0x5E),
|
||||
(0x05, 0x5E),
|
||||
(0x06, 0x5F),
|
||||
(0x07, 0x5F),
|
||||
(0x08, 0x5F),
|
||||
(0x09, 0x57),
|
||||
(0x0A, 0x57),
|
||||
(0x0B, 0x77),
|
||||
(0x0C, 0x77),
|
||||
(0x0D, 0x47),
|
||||
(0x0E, 0x47),
|
||||
(0x0F, 0x45),
|
||||
(0x10, 0x45),
|
||||
(0x11, 0x4B),
|
||||
(0x12, 0x4B),
|
||||
(0x13, 0x49),
|
||||
(0x14, 0x49),
|
||||
(0x15, 0x5F),
|
||||
(0x16, 0x41),
|
||||
(0x17, 0x41),
|
||||
(0x18, 0x40),
|
||||
(0x19, 0x40),
|
||||
(0x1A, 0x5E),
|
||||
(0x1B, 0x5E),
|
||||
(0x1C, 0x5F),
|
||||
(0x1D, 0x5F),
|
||||
(0x1E, 0x5F),
|
||||
(0x1F, 0x57),
|
||||
(0x20, 0x57),
|
||||
(0x21, 0x77),
|
||||
(0x22, 0x77),
|
||||
(0x23, 0x46),
|
||||
(0x24, 0x46),
|
||||
(0x25, 0x44),
|
||||
(0x26, 0x44),
|
||||
(0x27, 0x4A),
|
||||
(0x28, 0x4A),
|
||||
(0x29, 0x48),
|
||||
(0x2A, 0x48),
|
||||
(0x2B, 0x5F),
|
||||
(0x2C, 0x01),
|
||||
(0x2D, 0x01),
|
||||
(0x2E, 0x00),
|
||||
(0x2F, 0x00),
|
||||
(0x30, 0x1F),
|
||||
(0x31, 0x1F),
|
||||
(0x32, 0x1E),
|
||||
(0x33, 0x1E),
|
||||
(0x34, 0x1F),
|
||||
(0x35, 0x17),
|
||||
(0x36, 0x17),
|
||||
(0x37, 0x37),
|
||||
(0x38, 0x37),
|
||||
(0x39, 0x08),
|
||||
(0x3A, 0x08),
|
||||
(0x3B, 0x0A),
|
||||
(0x3C, 0x0A),
|
||||
(0x3D, 0x04),
|
||||
(0x3E, 0x04),
|
||||
(0x3F, 0x06),
|
||||
(0x40, 0x06),
|
||||
(0x41, 0x1F),
|
||||
(0x42, 0x02),
|
||||
(0x43, 0x02),
|
||||
(0x44, 0x00),
|
||||
(0x45, 0x00),
|
||||
(0x46, 0x1F),
|
||||
(0x47, 0x1F),
|
||||
(0x48, 0x1E),
|
||||
(0x49, 0x1E),
|
||||
(0x4A, 0x1F),
|
||||
(0x4B, 0x17),
|
||||
(0x4C, 0x17),
|
||||
(0x4D, 0x37),
|
||||
(0x4E, 0x37),
|
||||
(0x4F, 0x09),
|
||||
(0x50, 0x09),
|
||||
(0x51, 0x0B),
|
||||
(0x52, 0x0B),
|
||||
(0x53, 0x05),
|
||||
(0x54, 0x05),
|
||||
(0x55, 0x07),
|
||||
(0x56, 0x07),
|
||||
(0x57, 0x1F),
|
||||
(0x58, 0x40),
|
||||
(0x5B, 0x30),
|
||||
(0x5C, 0x00),
|
||||
(0x5D, 0x34),
|
||||
(0x5E, 0x05),
|
||||
(0x5F, 0x02),
|
||||
(0x63, 0x00),
|
||||
(0x64, 0x6A),
|
||||
(0x67, 0x73),
|
||||
(0x68, 0x07),
|
||||
(0x69, 0x08),
|
||||
(0x6A, 0x6A),
|
||||
(0x6B, 0x08),
|
||||
(0x6C, 0x00),
|
||||
(0x6D, 0x00),
|
||||
(0x6E, 0x00),
|
||||
(0x6F, 0x88),
|
||||
(0x75, 0xFF),
|
||||
(0x77, 0xDD),
|
||||
(0x78, 0x2C),
|
||||
(0x79, 0x15),
|
||||
(0x7A, 0x17),
|
||||
(0x7D, 0x14),
|
||||
(0x7E, 0x82),
|
||||
(0xE0, 0x04),
|
||||
(0x00, 0x0E),
|
||||
(0x02, 0xB3),
|
||||
(0x09, 0x60),
|
||||
(0x0E, 0x48),
|
||||
(0x37, 0x58),
|
||||
(0x2B, 0x0F),
|
||||
(0xE0, 0x05),
|
||||
(0x15, 0x1D),
|
||||
(0xE0, 0x00),
|
||||
(0xE6, 0x02),
|
||||
(0xE7, 0x0C)
|
||||
]
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifndef USE_BK72XX
|
||||
|
||||
#include "sensor_mlx90393.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -270,3 +272,5 @@ void MLX90393Cls::verify_settings_timeout_(MLX90393Setting stage) {
|
||||
}
|
||||
|
||||
} // namespace esphome::mlx90393
|
||||
|
||||
#endif // USE_BK72XX
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef USE_BK72XX
|
||||
|
||||
#include <MLX90393.h>
|
||||
#include <MLX90393Hal.h>
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
@@ -76,3 +78,5 @@ class MLX90393Cls final : public PollingComponent, public i2c::I2CDevice, public
|
||||
};
|
||||
|
||||
} // namespace esphome::mlx90393
|
||||
|
||||
#endif // USE_BK72XX
|
||||
|
||||
@@ -71,15 +71,6 @@ struct IPAddress {
|
||||
bool is_ip4() const { return false; }
|
||||
bool is_ip6() const { return this->is_set(); }
|
||||
bool is_multicast() const { return net_ipv6_is_addr_mcast(&ip_addr_); }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED(
|
||||
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
|
||||
"2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
return buf;
|
||||
}
|
||||
char *str_to(char *buf) const {
|
||||
if (inet_ntop(AF_INET6, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE) == nullptr)
|
||||
buf[0] = '\0';
|
||||
@@ -95,15 +86,6 @@ struct IPAddress {
|
||||
}
|
||||
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED(
|
||||
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
|
||||
"2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
return buf;
|
||||
}
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
char *str_to(char *buf) const {
|
||||
inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE);
|
||||
@@ -186,15 +168,6 @@ struct IPAddress {
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED(
|
||||
"str() is deprecated: use 'char buf[IP_ADDRESS_BUFFER_SIZE]; ip.str_to(buf);' instead. Removed in 2026.8.0",
|
||||
"2026.2.0")
|
||||
std::string str() const {
|
||||
char buf[IP_ADDRESS_BUFFER_SIZE];
|
||||
this->str_to(buf);
|
||||
return buf;
|
||||
}
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
/// Output is lowercased per RFC 5952 (IPv6 hex digits a-f).
|
||||
char *str_to(char *buf) const {
|
||||
|
||||
@@ -19,10 +19,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
},
|
||||
"nextion_upload_arduino.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.RP2_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,7 +4,14 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, esp32, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BRIGHTNESS, CONF_ID, CONF_LAMBDA, CONF_ON_TOUCH
|
||||
from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_ON_TOUCH,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
)
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
from . import ( # noqa: F401 pylint: disable=unused-import
|
||||
@@ -135,7 +142,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_TFT_UPLOAD_WATCHDOG_TIMEOUT
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_TFT_URL): cv.url,
|
||||
# TFT upload needs an HTTP client and runtime UART reconfiguration,
|
||||
# neither of which is implemented for the RP2 or host platforms.
|
||||
cv.Optional(CONF_TFT_URL): cv.All(
|
||||
cv.url,
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
]
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.Any(
|
||||
0, cv.int_range(min=3, max=65535)
|
||||
),
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
#elif defined(USE_ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif // USE_ESP32 vs USE_ESP8266
|
||||
#elif defined(USE_LIBRETINY)
|
||||
#include <HTTPClient.h>
|
||||
#endif // USE_ESP32 vs USE_ESP8266 vs USE_LIBRETINY
|
||||
#endif // USE_NEXTION_TFT_UPLOAD
|
||||
|
||||
namespace esphome::nextion {
|
||||
@@ -1564,7 +1566,7 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
|
||||
* @return position of last byte transferred, -1 for failure.
|
||||
*/
|
||||
int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start);
|
||||
#elif defined(USE_ARDUINO)
|
||||
#elif defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
/**
|
||||
* will request chunk_size chunks from the web server
|
||||
* and send each to the nextion
|
||||
@@ -1573,7 +1575,7 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
|
||||
* @return position of last byte transferred, -1 for failure.
|
||||
*/
|
||||
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start);
|
||||
#endif // USE_ESP32 vs USE_ARDUINO
|
||||
#endif // USE_ESP32 vs USE_ESP8266/USE_LIBRETINY
|
||||
|
||||
/**
|
||||
* Ends the upload process, restart Nextion and, if successful,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "nextion.h"
|
||||
|
||||
#ifdef USE_NEXTION_TFT_UPLOAD
|
||||
#ifndef USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include <cinttypes>
|
||||
#include "esphome/components/network/util.h"
|
||||
@@ -209,7 +209,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
http_client.setTimeout(this->tft_upload_http_timeout_);
|
||||
|
||||
bool begin_status = false;
|
||||
#ifdef USE_ESP8266
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
||||
http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
|
||||
@@ -219,7 +218,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
http_client.setRedirectLimit(3);
|
||||
#endif
|
||||
begin_status = http_client.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
|
||||
#endif // USE_ESP8266
|
||||
if (!begin_status) {
|
||||
this->connection_state_.is_updating_ = false;
|
||||
ESP_LOGD(TAG, "Connection failed");
|
||||
@@ -356,7 +354,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
return upload_end_(true);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
WiFiClient *Nextion::get_wifi_client_() {
|
||||
if (this->tft_url_.starts_with("https:")) {
|
||||
if (this->wifi_client_secure_ == nullptr) {
|
||||
@@ -374,9 +371,8 @@ WiFiClient *Nextion::get_wifi_client_() {
|
||||
}
|
||||
return this->wifi_client_;
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
} // namespace esphome::nextion
|
||||
|
||||
#endif // NOT USE_ESP32
|
||||
#endif // USE_ESP8266
|
||||
#endif // USE_NEXTION_TFT_UPLOAD
|
||||
|
||||
@@ -69,7 +69,12 @@ from .const import (
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
|
||||
)
|
||||
from .framework import check_and_install, get_build_env, get_build_paths
|
||||
from .framework import (
|
||||
check_and_install,
|
||||
get_build_env,
|
||||
get_build_paths,
|
||||
setup_platformio_python_env,
|
||||
)
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import nrf52_pin_to_code # noqa: F401
|
||||
@@ -514,6 +519,7 @@ def _upload_using_platformio(
|
||||
) -> int | str:
|
||||
from esphome.platformio import toolchain
|
||||
|
||||
setup_platformio_python_env()
|
||||
if port is not None:
|
||||
upload_args += ["--upload-port", port]
|
||||
return toolchain.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||
@@ -809,6 +815,10 @@ def _copy_if_exists(src: Path, dst: Path) -> None:
|
||||
|
||||
def run_compile(args, config: ConfigType) -> bool:
|
||||
if CORE.using_toolchain_platformio:
|
||||
# The actual build is done by PlatformIO (the caller falls through to
|
||||
# it when this returns False); prepare the Python environment its
|
||||
# Zephyr build script expects first.
|
||||
setup_platformio_python_env()
|
||||
return False
|
||||
if not CORE.using_toolchain_sdk_nrf:
|
||||
raise EsphomeError(
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import platformdirs
|
||||
@@ -27,6 +28,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
_REQUIREMENTS = Path(__file__).parent / "requirements.txt"
|
||||
TOOLCHAIN_VERSION = "0.17.4"
|
||||
|
||||
# Packages the PlatformIO toolchain's Zephyr build script needs beyond west
|
||||
# (which comes from requirements.txt). Keep the pin in sync with
|
||||
# framework-sdk-nrf scripts/platformio/platformio-build.py.
|
||||
_PLATFORMIO_PENV_REQUIREMENTS: tuple[str, ...] = ("cbor2==5.6.5",)
|
||||
|
||||
SDK_NG_TOOLCHAIN_MIRRORS = str_to_lst_of_str(
|
||||
os.environ.get(
|
||||
"ESPHOME_SDK_NG_TOOLCHAIN_MIRRORS",
|
||||
@@ -133,10 +139,94 @@ def get_build_env() -> dict:
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = str(venv_bin_dir) + os.pathsep + env.get("PATH", "")
|
||||
env["ZEPHYR_BASE"] = str(_get_framework_path(version) / "zephyr")
|
||||
env["Zephyr-sdk_DIR"] = str(_get_toolchain_path(TOOLCHAIN_VERSION) / "cmake")
|
||||
# ZEPHYR_SDK_INSTALL_DIR is the variable Zephyr documents for pointing at
|
||||
# the SDK: FindZephyr-sdk.cmake reads it (from the environment, via
|
||||
# zephyr_get) and passes it straight to find_package as a HINT. This
|
||||
# matters because the SDK lives in the esphome cache dir, which is not on
|
||||
# the module's static search path (/usr, /opt, $HOME, ...). A generic
|
||||
# "Zephyr-sdk_DIR" environment hint proved unreliable here: containerized
|
||||
# non-root builds failed to locate the SDK with it, while
|
||||
# ZEPHYR_SDK_INSTALL_DIR fixed the same invocation.
|
||||
env["ZEPHYR_SDK_INSTALL_DIR"] = str(_get_toolchain_path(TOOLCHAIN_VERSION))
|
||||
return env
|
||||
|
||||
|
||||
def _get_platformio_penv_path() -> Path:
|
||||
return get_sdk_nrf_tools_path() / "penvs" / "platformio"
|
||||
|
||||
|
||||
def _get_penv_site_packages(penv_path: Path) -> Path:
|
||||
if os.name == "nt":
|
||||
return penv_path / "Lib" / "site-packages"
|
||||
python_dir = f"python{sys.version_info.major}.{sys.version_info.minor}"
|
||||
return penv_path / "lib" / python_dir / "site-packages"
|
||||
|
||||
|
||||
def _prepend_env_path(name: str, entry: str) -> None:
|
||||
"""Prepend ``entry`` to the ``os.pathsep``-separated env var ``name``."""
|
||||
current = os.environ.get(name, "")
|
||||
entries = current.split(os.pathsep) if current else []
|
||||
if entry not in entries:
|
||||
os.environ[name] = os.pathsep.join([entry, *entries])
|
||||
|
||||
|
||||
def setup_platformio_python_env() -> None:
|
||||
"""Make the Zephyr build's Python packages available to PlatformIO.
|
||||
|
||||
The PlatformIO toolchain's Zephyr framework build script pip-installs
|
||||
west and cbor2 (and pyocd on x86_64) into the Python environment running
|
||||
PlatformIO whenever they are not importable. That environment is not
|
||||
always writable — for example the docker image run as a non-root user,
|
||||
where ESPHome lives in the system Python — so the install fails with
|
||||
"Permission denied". Instead, pre-install those packages into a dedicated
|
||||
venv under the sdk-nrf tools dir and expose it to the PlatformIO
|
||||
subprocesses through the environment:
|
||||
|
||||
* PYTHONPATH makes the venv's packages importable from the interpreter
|
||||
that runs PlatformIO/SCons, so the build script skips its installs.
|
||||
* VIRTUAL_ENV redirects any install the build script still performs via
|
||||
uv (pyocd is fetched on demand) into the writable venv.
|
||||
* PATH exposes console scripts installed into the venv (e.g. pyocd).
|
||||
"""
|
||||
penv_path = _get_platformio_penv_path()
|
||||
env_python_path = get_python_env_executable_path(penv_path, "python")
|
||||
sentinel = penv_path / ".ready"
|
||||
# Include the Python version: the venv breaks when the interpreter it
|
||||
# was created from is upgraded, so it must be rebuilt.
|
||||
requirements_hash = hashlib.sha256(
|
||||
_REQUIREMENTS.read_bytes()
|
||||
+ "\n".join(_PLATFORMIO_PENV_REQUIREMENTS).encode()
|
||||
+ f"python{sys.version_info.major}.{sys.version_info.minor}".encode()
|
||||
).hexdigest()
|
||||
if (
|
||||
not sentinel.exists()
|
||||
or sentinel.read_text(encoding="utf-8") != requirements_hash
|
||||
):
|
||||
rmdir(penv_path, msg="Clean up PlatformIO toolchain Python environment")
|
||||
|
||||
create_venv(penv_path, msg="PlatformIO toolchain")
|
||||
|
||||
_LOGGER.info("Installing PlatformIO toolchain requirements ...")
|
||||
cmd = [
|
||||
str(env_python_path),
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"-r",
|
||||
str(_REQUIREMENTS),
|
||||
*_PLATFORMIO_PENV_REQUIREMENTS,
|
||||
]
|
||||
if not run_command_ok(cmd):
|
||||
raise EsphomeError(
|
||||
"Install requirements for PlatformIO toolchain Python environment failure"
|
||||
)
|
||||
sentinel.write_text(requirements_hash, encoding="utf-8")
|
||||
|
||||
os.environ["VIRTUAL_ENV"] = str(penv_path)
|
||||
_prepend_env_path("PYTHONPATH", str(_get_penv_site_packages(penv_path)))
|
||||
_prepend_env_path("PATH", str(env_python_path.parent))
|
||||
|
||||
|
||||
def _patch_uf2conv_escape_sequences(framework_path: Path) -> None:
|
||||
# SDK v2.6.1 ships uf2conv.py with '\s+' — an unrecognised escape that
|
||||
# Python 3.12+ flags with SyntaxWarning (a future version will reject it).
|
||||
|
||||
@@ -27,11 +27,11 @@ uint8_t parse_u8_lb(OpenthermData &data) { return data.valueLB; }
|
||||
uint8_t parse_u8_hb(OpenthermData &data) { return data.valueHB; }
|
||||
int8_t parse_s8_lb(OpenthermData &data) { return (int8_t) data.valueLB; }
|
||||
int8_t parse_s8_hb(OpenthermData &data) { return (int8_t) data.valueHB; }
|
||||
uint16_t parse_u16(OpenthermData &data) { return data.u16(); }
|
||||
uint16_t parse_u16(OpenthermData &data) { return data.get_u16(); }
|
||||
uint16_t parse_u8_lb_60(OpenthermData &data) { return data.valueLB * 60; }
|
||||
uint16_t parse_u8_hb_60(OpenthermData &data) { return data.valueHB * 60; }
|
||||
int16_t parse_s16(OpenthermData &data) { return data.s16(); }
|
||||
float parse_f88(OpenthermData &data) { return data.f88(); }
|
||||
int16_t parse_s16(OpenthermData &data) { return data.get_s16(); }
|
||||
float parse_f88(OpenthermData &data) { return data.get_f88(); }
|
||||
|
||||
void write_flag8_lb_0(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 0, value); }
|
||||
void write_flag8_lb_1(const bool value, OpenthermData &data) { data.valueLB = write_bit(data.valueLB, 1, value); }
|
||||
@@ -53,9 +53,9 @@ void write_u8_lb(const uint8_t value, OpenthermData &data) { data.valueLB = valu
|
||||
void write_u8_hb(const uint8_t value, OpenthermData &data) { data.valueHB = value; }
|
||||
void write_s8_lb(const int8_t value, OpenthermData &data) { data.valueLB = (uint8_t) value; }
|
||||
void write_s8_hb(const int8_t value, OpenthermData &data) { data.valueHB = (uint8_t) value; }
|
||||
void write_u16(const uint16_t value, OpenthermData &data) { data.u16(value); }
|
||||
void write_s16(const int16_t value, OpenthermData &data) { data.s16(value); }
|
||||
void write_f88(const float value, OpenthermData &data) { data.f88(value); }
|
||||
void write_u16(const uint16_t value, OpenthermData &data) { data.set_u16(value); }
|
||||
void write_s16(const int16_t value, OpenthermData &data) { data.set_s16(value); }
|
||||
void write_f88(const float value, OpenthermData &data) { data.set_f88(value); }
|
||||
|
||||
} // namespace message_data
|
||||
|
||||
|
||||
@@ -533,34 +533,34 @@ void OpenTherm::debug_data(OpenthermData &data) {
|
||||
ESP_LOGD(TAG, "%s %s %s %s", format_bin_to(type_buf, data.type), format_bin_to(id_buf, data.id),
|
||||
format_bin_to(hb_buf, data.valueHB), format_bin_to(lb_buf, data.valueLB));
|
||||
ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
|
||||
this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(),
|
||||
data.f88());
|
||||
this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.get_u16(),
|
||||
data.get_f88());
|
||||
}
|
||||
void OpenTherm::debug_error(OpenThermError &error) const {
|
||||
ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_,
|
||||
error.capture, error.bit_pos);
|
||||
}
|
||||
|
||||
float OpenthermData::f88() { return ((float) this->s16()) / 256.0f; }
|
||||
float OpenthermData::get_f88() { return ((float) this->get_s16()) / 256.0f; }
|
||||
|
||||
void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); }
|
||||
void OpenthermData::set_f88(float value) { this->set_s16((int16_t) (value * 256)); }
|
||||
|
||||
uint16_t OpenthermData::u16() {
|
||||
uint16_t OpenthermData::get_u16() {
|
||||
uint16_t const value = this->valueHB;
|
||||
return (value << 8) | this->valueLB;
|
||||
}
|
||||
|
||||
void OpenthermData::u16(uint16_t value) {
|
||||
void OpenthermData::set_u16(uint16_t value) {
|
||||
this->valueLB = value & 0xFF;
|
||||
this->valueHB = (value >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
int16_t OpenthermData::s16() {
|
||||
int16_t OpenthermData::get_s16() {
|
||||
int16_t const value = this->valueHB;
|
||||
return (value << 8) | this->valueLB;
|
||||
}
|
||||
|
||||
void OpenthermData::s16(int16_t value) {
|
||||
void OpenthermData::set_s16(int16_t value) {
|
||||
this->valueLB = value & 0xFF;
|
||||
this->valueHB = (value >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ enum BitPositions { STOP_BIT = 33 };
|
||||
|
||||
/**
|
||||
* Structure to hold Opentherm data packet content.
|
||||
* Use f88(), u16() or s16() functions to get appropriate value of data packet accoridng to id of message.
|
||||
* Use get_f88(), get_u16() or get_s16() functions to get appropriate value of data packet according to id of message.
|
||||
*/
|
||||
struct OpenthermData {
|
||||
uint8_t type;
|
||||
@@ -191,32 +191,32 @@ struct OpenthermData {
|
||||
/**
|
||||
* @return float representation of data packet value
|
||||
*/
|
||||
float f88();
|
||||
float get_f88();
|
||||
|
||||
/**
|
||||
* @param float number to set as value of this data packet
|
||||
*/
|
||||
void f88(float value);
|
||||
void set_f88(float value);
|
||||
|
||||
/**
|
||||
* @return unsigned 16b integer representation of data packet value
|
||||
*/
|
||||
uint16_t u16();
|
||||
uint16_t get_u16();
|
||||
|
||||
/**
|
||||
* @param unsigned 16b integer number to set as value of this data packet
|
||||
*/
|
||||
void u16(uint16_t value);
|
||||
void set_u16(uint16_t value);
|
||||
|
||||
/**
|
||||
* @return signed 16b integer representation of data packet value
|
||||
*/
|
||||
int16_t s16();
|
||||
int16_t get_s16();
|
||||
|
||||
/**
|
||||
* @param signed 16b integer number to set as value of this data packet
|
||||
*/
|
||||
void s16(int16_t value);
|
||||
void set_s16(int16_t value);
|
||||
};
|
||||
|
||||
struct OpenThermError {
|
||||
|
||||
@@ -16,7 +16,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RC522I2C),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x2C))
|
||||
).extend(i2c.i2c_device_schema(0x28))
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <pico.h>
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-int,readability-identifier-naming,readability-redundant-declaration)
|
||||
extern "C" unsigned long ulMainGetRunTimeCounterValue();
|
||||
|
||||
namespace esphome::rp2 {} // namespace esphome::rp2
|
||||
|
||||
@@ -64,7 +64,7 @@ static struct CrashData {
|
||||
uint32_t sp;
|
||||
uint32_t backtrace[MAX_BACKTRACE];
|
||||
uint8_t backtrace_count;
|
||||
} s_crash_data __attribute__((section(".noinit")));
|
||||
} s_crash_data __attribute__((section(".noinit"))); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
bool crash_handler_has_data() { return s_crash_data.valid; }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user