mirror of
https://github.com/esphome/esphome.git
synced 2026-05-30 23:54:04 +08:00
Merge remote-tracking branch 'upstream/dev' into pr-15656
# Conflicts: # esphome/core/application.cpp # esphome/core/application.h
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
f31f13994768b5b07e29624406c9b053bf4bb26e1623ac2bc1e9d4a9477502d6
|
dc8ad5472d9fb44ce1ca29a0601afd65705642799a2819704dfc8459fbaf9815
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ runs:
|
|||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
label:
|
label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
if: github.event.pull_request.state == 'open' && (github.event.action != 'labeled' || github.event.sender.type != 'Bot')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v2
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
|||||||
+15
-15
@@ -47,7 +47,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -159,7 +159,7 @@ jobs:
|
|||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -198,7 +198,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Restore components graph cache
|
- name: Restore components graph cache
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -231,7 +231,7 @@ jobs:
|
|||||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||||
- name: Save components graph cache
|
- name: Save components graph cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -253,7 +253,7 @@ jobs:
|
|||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -387,14 +387,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -466,14 +466,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -555,14 +555,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -817,7 +817,7 @@ jobs:
|
|||||||
- name: Restore cached memory analysis
|
- name: Restore cached memory analysis
|
||||||
id: cache-memory-analysis
|
id: cache-memory-analysis
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -841,7 +841,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -883,7 +883,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save memory analysis to cache
|
- name: Save memory analysis to cache
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -930,7 +930,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
@@ -256,7 +256,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
@@ -287,7 +287,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
|||||||
+8
-1
@@ -750,8 +750,15 @@ def upload_using_esptool(
|
|||||||
platformio_api.FlashImage(
|
platformio_api.FlashImage(
|
||||||
path=idedata.firmware_bin_path, offset=firmware_offset
|
path=idedata.firmware_bin_path, offset=firmware_offset
|
||||||
),
|
),
|
||||||
*idedata.extra_flash_images,
|
|
||||||
]
|
]
|
||||||
|
for image in idedata.extra_flash_images:
|
||||||
|
if not image.path.is_file():
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Skipping missing flash image declared by platform: %s",
|
||||||
|
image.path,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
flash_images.append(image)
|
||||||
|
|
||||||
mcu = "esp8266"
|
mcu = "esp8266"
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import logging
|
|||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.components.esp32 import get_esp32_variant, include_builtin_idf_component
|
from esphome.components.esp32 import (
|
||||||
|
get_esp32_variant,
|
||||||
|
include_builtin_idf_component,
|
||||||
|
require_adc_oneshot_iram,
|
||||||
|
)
|
||||||
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
|
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
|
||||||
from esphome.components.zephyr import (
|
from esphome.components.zephyr import (
|
||||||
zephyr_add_overlay,
|
zephyr_add_overlay,
|
||||||
@@ -24,6 +28,7 @@ from esphome.const import (
|
|||||||
PlatformFramework,
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
ATTENUATION_MODES,
|
ATTENUATION_MODES,
|
||||||
@@ -65,6 +70,13 @@ def validate_config(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _require_adc_iram(config: ConfigType) -> ConfigType:
|
||||||
|
"""Register ADC oneshot IRAM requirement during config validation."""
|
||||||
|
if CORE.is_esp32:
|
||||||
|
require_adc_oneshot_iram()
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
ADCSensor = adc_ns.class_(
|
ADCSensor = adc_ns.class_(
|
||||||
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
)
|
)
|
||||||
@@ -95,6 +107,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s")),
|
.extend(cv.polling_component_schema("60s")),
|
||||||
validate_config,
|
validate_config,
|
||||||
|
_require_adc_iram,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_ADC_CHANNEL_ID = "adc_channel_id"
|
CONF_ADC_CHANNEL_ID = "adc_channel_id"
|
||||||
|
|||||||
@@ -671,6 +671,7 @@ message SensorStateResponse {
|
|||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (ifdef) = "USE_SENSOR";
|
option (ifdef) = "USE_SENSOR";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (speed_optimized) = true;
|
||||||
|
|
||||||
fixed32 key = 1 [(force) = true];
|
fixed32 key = 1 [(force) = true];
|
||||||
float state = 2;
|
float state = 2;
|
||||||
@@ -777,9 +778,10 @@ message SubscribeLogsResponse {
|
|||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (log) = false;
|
option (log) = false;
|
||||||
option (no_delay) = false;
|
option (no_delay) = false;
|
||||||
|
option (speed_optimized) = true;
|
||||||
|
|
||||||
LogLevel level = 1;
|
LogLevel level = 1 [(force) = true];
|
||||||
bytes message = 3;
|
bytes message = 3 [(force) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== NOISE ENCRYPTION ====================
|
// ==================== NOISE ENCRYPTION ====================
|
||||||
@@ -1638,6 +1640,7 @@ message BluetoothLERawAdvertisementsResponse {
|
|||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (speed_optimized) = true;
|
||||||
|
|
||||||
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
|
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ extend google.protobuf.MessageOptions {
|
|||||||
optional bool no_delay = 1040 [default=false];
|
optional bool no_delay = 1040 [default=false];
|
||||||
optional string base_class = 1041;
|
optional string base_class = 1041;
|
||||||
optional bool inline_encode = 1042 [default=false];
|
optional bool inline_encode = 1042 [default=false];
|
||||||
|
optional bool speed_optimized = 1043 [default=false];
|
||||||
}
|
}
|
||||||
|
|
||||||
extend google.protobuf.FieldOptions {
|
extend google.protobuf.FieldOptions {
|
||||||
|
|||||||
@@ -745,7 +745,9 @@ uint32_t ListEntitiesSensorResponse::calculate_size() const {
|
|||||||
#endif
|
#endif
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
uint8_t *SensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint8_t *
|
||||||
|
SensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||||
ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key);
|
ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key);
|
||||||
ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state);
|
ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state);
|
||||||
@@ -755,7 +757,9 @@ uint8_t *SensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG
|
|||||||
#endif
|
#endif
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
uint32_t SensorStateResponse::calculate_size() const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint32_t
|
||||||
|
SensorStateResponse::calculate_size() const {
|
||||||
uint32_t size = 0;
|
uint32_t size = 0;
|
||||||
size += 5;
|
size += 5;
|
||||||
size += ProtoSize::calc_float(1, this->state);
|
size += ProtoSize::calc_float(1, this->state);
|
||||||
@@ -912,16 +916,22 @@ bool SubscribeLogsRequest::decode_varint(uint32_t field_id, proto_varint_value_t
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
uint8_t *SubscribeLogsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint8_t *
|
||||||
|
SubscribeLogsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(this->level));
|
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(this->level), true);
|
||||||
ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, this->message_ptr_, this->message_len_);
|
ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 26);
|
||||||
|
ProtoEncode::encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, this->message_len_);
|
||||||
|
ProtoEncode::encode_raw(pos PROTO_ENCODE_DEBUG_ARG, this->message_ptr_, this->message_len_);
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
uint32_t SubscribeLogsResponse::calculate_size() const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint32_t
|
||||||
|
SubscribeLogsResponse::calculate_size() const {
|
||||||
uint32_t size = 0;
|
uint32_t size = 0;
|
||||||
size += this->level ? 2 : 0;
|
size += 2;
|
||||||
size += ProtoSize::calc_length(1, this->message_len_);
|
size += ProtoSize::calc_length_force(1, this->message_len_);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
@@ -2328,7 +2338,9 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id,
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
uint8_t *BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint8_t *
|
||||||
|
BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||||
auto &sub_msg = this->advertisements[i];
|
auto &sub_msg = this->advertisements[i];
|
||||||
@@ -2350,7 +2362,9 @@ uint8_t *BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer P
|
|||||||
}
|
}
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
uint32_t BluetoothLERawAdvertisementsResponse::calculate_size() const {
|
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||||
|
uint32_t
|
||||||
|
BluetoothLERawAdvertisementsResponse::calculate_size() const {
|
||||||
uint32_t size = 0;
|
uint32_t size = 0;
|
||||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||||
auto &sub_msg = this->advertisements[i];
|
auto &sub_msg = this->advertisements[i];
|
||||||
|
|||||||
@@ -111,14 +111,14 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
#endif
|
#endif
|
||||||
float get_reference_voltage(uint8_t phase) {
|
float get_reference_voltage(uint8_t phase) {
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
return (phase >= 0 && phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0; // Default voltage
|
return (phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0; // Default voltage
|
||||||
#else
|
#else
|
||||||
return 120.0; // Default voltage
|
return 120.0; // Default voltage
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
float get_reference_current(uint8_t phase) {
|
float get_reference_current(uint8_t phase) {
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
return (phase >= 0 && phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f; // Default current
|
return (phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f; // Default current
|
||||||
#else
|
#else
|
||||||
return 5.0f; // Default current
|
return 5.0f; // Default current
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import add_idf_component, include_builtin_idf_component
|
from esphome.components.esp32 import (
|
||||||
|
add_idf_component,
|
||||||
|
add_idf_sdkconfig_option,
|
||||||
|
include_builtin_idf_component,
|
||||||
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
@@ -27,6 +31,7 @@ class AudioData:
|
|||||||
flac_support: bool = False
|
flac_support: bool = False
|
||||||
mp3_support: bool = False
|
mp3_support: bool = False
|
||||||
opus_support: bool = False
|
opus_support: bool = False
|
||||||
|
micro_decoder_support: bool = False
|
||||||
|
|
||||||
|
|
||||||
def _get_data() -> AudioData:
|
def _get_data() -> AudioData:
|
||||||
@@ -50,6 +55,11 @@ def request_opus_support() -> None:
|
|||||||
_get_data().opus_support = True
|
_get_data().opus_support = True
|
||||||
|
|
||||||
|
|
||||||
|
def request_micro_decoder_support() -> None:
|
||||||
|
"""Request micro-decoder library support for audio decoding."""
|
||||||
|
_get_data().micro_decoder_support = True
|
||||||
|
|
||||||
|
|
||||||
CONF_MIN_BITS_PER_SAMPLE = "min_bits_per_sample"
|
CONF_MIN_BITS_PER_SAMPLE = "min_bits_per_sample"
|
||||||
CONF_MAX_BITS_PER_SAMPLE = "max_bits_per_sample"
|
CONF_MAX_BITS_PER_SAMPLE = "max_bits_per_sample"
|
||||||
CONF_MIN_CHANNELS = "min_channels"
|
CONF_MIN_CHANNELS = "min_channels"
|
||||||
@@ -208,6 +218,19 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
data = _get_data()
|
data = _get_data()
|
||||||
|
|
||||||
|
if data.micro_decoder_support:
|
||||||
|
add_idf_component(name="esphome/micro-decoder", ref="0.1.1")
|
||||||
|
|
||||||
|
# All codecs are enabled by default in micro-decoder, so disable the ones that aren't requested to save flash
|
||||||
|
if not data.flac_support:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_FLAC", False)
|
||||||
|
if not data.mp3_support:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_MP3", False)
|
||||||
|
if not data.opus_support:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_OPUS", False)
|
||||||
|
|
||||||
|
# Legacy audio_decoder.cpp support defines and components
|
||||||
if data.flac_support:
|
if data.flac_support:
|
||||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT")
|
cg.add_define("USE_AUDIO_FLAC_SUPPORT")
|
||||||
add_idf_component(name="esphome/micro-flac", ref="0.1.1")
|
add_idf_component(name="esphome/micro-flac", ref="0.1.1")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
|
|||||||
DOMAIN = "bme68x_bsec2"
|
DOMAIN = "bme68x_bsec2"
|
||||||
|
|
||||||
BSEC2_LIBRARY_VERSION = "1.10.2610"
|
BSEC2_LIBRARY_VERSION = "1.10.2610"
|
||||||
|
BME68x_LIBRARY_VERSION = "v1.3.40408"
|
||||||
|
|
||||||
CONF_ALGORITHM_OUTPUT = "algorithm_output"
|
CONF_ALGORITHM_OUTPUT = "algorithm_output"
|
||||||
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
|
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
|
||||||
@@ -184,10 +185,25 @@ async def to_code_base(config):
|
|||||||
if core.CORE.using_arduino:
|
if core.CORE.using_arduino:
|
||||||
cg.add_library("Wire", None)
|
cg.add_library("Wire", None)
|
||||||
cg.add_library("SPI", None)
|
cg.add_library("SPI", None)
|
||||||
|
|
||||||
|
if core.CORE.is_esp32:
|
||||||
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
|
||||||
|
add_idf_component(
|
||||||
|
name="boschsensortec/Bosch-BME68x-Library",
|
||||||
|
repo="https://github.com/esphome-libs/Bosch-BME68x-Library",
|
||||||
|
ref=BME68x_LIBRARY_VERSION,
|
||||||
|
)
|
||||||
|
add_idf_component(
|
||||||
|
name="boschsensortec/Bosch-BSEC2-Library",
|
||||||
|
repo="https://github.com/esphome-libs/Bosch-BSEC2-Library",
|
||||||
|
ref=BSEC2_LIBRARY_VERSION,
|
||||||
|
)
|
||||||
|
else:
|
||||||
cg.add_library(
|
cg.add_library(
|
||||||
"BME68x Sensor library",
|
"BME68x Sensor library",
|
||||||
None,
|
None,
|
||||||
"https://github.com/boschsensortec/Bosch-BME68x-Library#v1.3.40408",
|
f"https://github.com/boschsensortec/Bosch-BME68x-Library#{BME68x_LIBRARY_VERSION}",
|
||||||
)
|
)
|
||||||
cg.add_library(
|
cg.add_library(
|
||||||
"BSEC2 Software Library",
|
"BSEC2 Software Library",
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
|||||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||||
|
|
||||||
if (can_id := config.get(CONF_CAN_ID)) is not None:
|
if (can_id := config.get(CONF_CAN_ID)) is not None:
|
||||||
can_id = await cg.templatable(can_id, args, cg.uint32)
|
|
||||||
cg.add(var.set_can_id(can_id))
|
cg.add(var.set_can_id(can_id))
|
||||||
cg.add(var.set_use_extended_id(config[CONF_USE_EXTENDED_ID]))
|
cg.add(var.set_use_extended_id(config[CONF_USE_EXTENDED_ID]))
|
||||||
|
|
||||||
|
|||||||
@@ -671,11 +671,12 @@ def _is_framework_url(source: str) -> bool:
|
|||||||
# The default/recommended arduino framework version
|
# The default/recommended arduino framework version
|
||||||
# - https://github.com/espressif/arduino-esp32/releases
|
# - https://github.com/espressif/arduino-esp32/releases
|
||||||
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
||||||
"recommended": cv.Version(3, 3, 7),
|
"recommended": cv.Version(3, 3, 8),
|
||||||
"latest": cv.Version(3, 3, 7),
|
"latest": cv.Version(3, 3, 8),
|
||||||
"dev": cv.Version(3, 3, 7),
|
"dev": cv.Version(3, 3, 8),
|
||||||
}
|
}
|
||||||
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
||||||
|
cv.Version(3, 3, 8): cv.Version(55, 3, 38, "1"),
|
||||||
cv.Version(3, 3, 7): cv.Version(55, 3, 37),
|
cv.Version(3, 3, 7): cv.Version(55, 3, 37),
|
||||||
cv.Version(3, 3, 6): cv.Version(55, 3, 36),
|
cv.Version(3, 3, 6): cv.Version(55, 3, 36),
|
||||||
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
|
cv.Version(3, 3, 5): cv.Version(55, 3, 35),
|
||||||
@@ -695,6 +696,7 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
|||||||
# These versions correspond to pioarduino/esp-idf releases
|
# These versions correspond to pioarduino/esp-idf releases
|
||||||
# See: https://github.com/pioarduino/esp-idf/releases
|
# See: https://github.com/pioarduino/esp-idf/releases
|
||||||
ARDUINO_IDF_VERSION_LOOKUP = {
|
ARDUINO_IDF_VERSION_LOOKUP = {
|
||||||
|
cv.Version(3, 3, 8): cv.Version(5, 5, 4),
|
||||||
cv.Version(3, 3, 7): cv.Version(5, 5, 3, "1"),
|
cv.Version(3, 3, 7): cv.Version(5, 5, 3, "1"),
|
||||||
cv.Version(3, 3, 6): cv.Version(5, 5, 2),
|
cv.Version(3, 3, 6): cv.Version(5, 5, 2),
|
||||||
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
|
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
|
||||||
@@ -714,17 +716,15 @@ ARDUINO_IDF_VERSION_LOOKUP = {
|
|||||||
# The default/recommended esp-idf framework version
|
# The default/recommended esp-idf framework version
|
||||||
# - https://github.com/espressif/esp-idf/releases
|
# - https://github.com/espressif/esp-idf/releases
|
||||||
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||||
"recommended": cv.Version(5, 5, 3, "1"),
|
"recommended": cv.Version(5, 5, 4),
|
||||||
"latest": cv.Version(5, 5, 3, "1"),
|
"latest": cv.Version(5, 5, 4),
|
||||||
"dev": cv.Version(5, 5, 4),
|
"dev": cv.Version(5, 5, 4),
|
||||||
}
|
}
|
||||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||||
cv.Version(
|
cv.Version(
|
||||||
6, 0, 0
|
6, 0, 0
|
||||||
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
|
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
|
||||||
cv.Version(
|
cv.Version(5, 5, 4): cv.Version(55, 3, 38, "1"),
|
||||||
5, 5, 4
|
|
||||||
): "https://github.com/pioarduino/platform-espressif32.git#develop",
|
|
||||||
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
|
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
|
||||||
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
|
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
|
||||||
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
|
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
|
||||||
@@ -744,8 +744,8 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
|||||||
# The platform-espressif32 version
|
# The platform-espressif32 version
|
||||||
# - https://github.com/pioarduino/platform-espressif32/releases
|
# - https://github.com/pioarduino/platform-espressif32/releases
|
||||||
PLATFORM_VERSION_LOOKUP = {
|
PLATFORM_VERSION_LOOKUP = {
|
||||||
"recommended": cv.Version(55, 3, 37),
|
"recommended": cv.Version(55, 3, 38, "1"),
|
||||||
"latest": cv.Version(55, 3, 37),
|
"latest": cv.Version(55, 3, 38, "1"),
|
||||||
"dev": "https://github.com/pioarduino/platform-espressif32.git#develop",
|
"dev": "https://github.com/pioarduino/platform-espressif32.git#develop",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,6 +1058,7 @@ CONF_DISABLE_MBEDTLS_PEER_CERT = "disable_mbedtls_peer_cert"
|
|||||||
CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7"
|
CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7"
|
||||||
CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram"
|
CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram"
|
||||||
CONF_DISABLE_FATFS = "disable_fatfs"
|
CONF_DISABLE_FATFS = "disable_fatfs"
|
||||||
|
CONF_ADC_ONESHOT_IN_IRAM = "adc_oneshot_in_iram"
|
||||||
|
|
||||||
# VFS requirement tracking
|
# VFS requirement tracking
|
||||||
# Components that need VFS features can call require_vfs_*() functions
|
# Components that need VFS features can call require_vfs_*() functions
|
||||||
@@ -1071,6 +1072,7 @@ KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required"
|
|||||||
KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required"
|
KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required"
|
||||||
KEY_FATFS_REQUIRED = "fatfs_required"
|
KEY_FATFS_REQUIRED = "fatfs_required"
|
||||||
KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required"
|
KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required"
|
||||||
|
KEY_ADC_ONESHOT_IRAM_REQUIRED = "adc_oneshot_iram_required"
|
||||||
|
|
||||||
|
|
||||||
def require_vfs_select() -> None:
|
def require_vfs_select() -> None:
|
||||||
@@ -1168,6 +1170,17 @@ def require_fatfs() -> None:
|
|||||||
CORE.data[KEY_ESP32][KEY_FATFS_REQUIRED] = True
|
CORE.data[KEY_ESP32][KEY_FATFS_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_adc_oneshot_iram() -> None:
|
||||||
|
"""Mark that ADC oneshot IRAM safety is required by a component.
|
||||||
|
|
||||||
|
Call this from components that use the ADC oneshot driver. When flash cache is
|
||||||
|
disabled (e.g., during NVS writes by WiFi, BLE, Zigbee, or power management),
|
||||||
|
the ADC oneshot read function must be in IRAM to avoid crashes.
|
||||||
|
This sets CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_ADC_ONESHOT_IRAM_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
def _parse_idf_component(value: str) -> ConfigType:
|
def _parse_idf_component(value: str) -> ConfigType:
|
||||||
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
||||||
# Match operator followed by version-like string (digit or *)
|
# Match operator followed by version-like string (digit or *)
|
||||||
@@ -1268,6 +1281,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
|||||||
cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean,
|
cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean,
|
cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean,
|
cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_ADC_ONESHOT_IN_IRAM, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_DISABLE_FATFS, default=True): cv.boolean,
|
cv.Optional(CONF_DISABLE_FATFS, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -2068,6 +2082,16 @@ async def to_code(config):
|
|||||||
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
||||||
add_idf_sdkconfig_option("CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM", False)
|
add_idf_sdkconfig_option("CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM", False)
|
||||||
|
|
||||||
|
# Place ADC oneshot control functions in IRAM for cache safety
|
||||||
|
# When flash cache is disabled (during NVS writes by WiFi, BLE, Zigbee, Thread,
|
||||||
|
# power management, etc.), ADC reads will crash if these functions are in flash.
|
||||||
|
# Components using ADC call require_adc_oneshot_iram() to force this.
|
||||||
|
if (
|
||||||
|
CORE.data[KEY_ESP32].get(KEY_ADC_ONESHOT_IRAM_REQUIRED, False)
|
||||||
|
or advanced[CONF_ADC_ONESHOT_IN_IRAM]
|
||||||
|
):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM", True)
|
||||||
|
|
||||||
# Disable FATFS support
|
# Disable FATFS support
|
||||||
# Components that need FATFS (SD card, etc.) can call require_fatfs()
|
# Components that need FATFS (SD card, etc.) can call require_fatfs()
|
||||||
if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False):
|
if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False):
|
||||||
|
|||||||
@@ -1960,6 +1960,10 @@ BOARDS = {
|
|||||||
"name": "Hornbill ESP32 Minima",
|
"name": "Hornbill ESP32 Minima",
|
||||||
"variant": VARIANT_ESP32,
|
"variant": VARIANT_ESP32,
|
||||||
},
|
},
|
||||||
|
"huidu_hd_wf1": {
|
||||||
|
"name": "Huidu HD-WF1",
|
||||||
|
"variant": VARIANT_ESP32S2,
|
||||||
|
},
|
||||||
"huidu_hd_wf2": {
|
"huidu_hd_wf2": {
|
||||||
"name": "Huidu HD-WF2",
|
"name": "Huidu HD-WF2",
|
||||||
"variant": VARIANT_ESP32S3,
|
"variant": VARIANT_ESP32S3,
|
||||||
@@ -2028,6 +2032,10 @@ BOARDS = {
|
|||||||
"name": "LilyGo T-Display-S3",
|
"name": "LilyGo T-Display-S3",
|
||||||
"variant": VARIANT_ESP32S3,
|
"variant": VARIANT_ESP32S3,
|
||||||
},
|
},
|
||||||
|
"lilygo-t-energy-s3": {
|
||||||
|
"name": "LilyGo T-Energy-S3",
|
||||||
|
"variant": VARIANT_ESP32S3,
|
||||||
|
},
|
||||||
"lilygo-t3-s3": {
|
"lilygo-t3-s3": {
|
||||||
"name": "LilyGo T3-S3",
|
"name": "LilyGo T3-S3",
|
||||||
"variant": VARIANT_ESP32S3,
|
"variant": VARIANT_ESP32S3,
|
||||||
@@ -2289,10 +2297,18 @@ BOARDS = {
|
|||||||
"name": "S.ODI Ultra v1",
|
"name": "S.ODI Ultra v1",
|
||||||
"variant": VARIANT_ESP32,
|
"variant": VARIANT_ESP32,
|
||||||
},
|
},
|
||||||
|
"seeed_xiao_esp32_s3_plus": {
|
||||||
|
"name": "Seeed Studio XIAO ESP32S3 Plus",
|
||||||
|
"variant": VARIANT_ESP32S3,
|
||||||
|
},
|
||||||
"seeed_xiao_esp32c3": {
|
"seeed_xiao_esp32c3": {
|
||||||
"name": "Seeed Studio XIAO ESP32C3",
|
"name": "Seeed Studio XIAO ESP32C3",
|
||||||
"variant": VARIANT_ESP32C3,
|
"variant": VARIANT_ESP32C3,
|
||||||
},
|
},
|
||||||
|
"seeed_xiao_esp32c5": {
|
||||||
|
"name": "Seeed Studio XIAO ESP32C5",
|
||||||
|
"variant": VARIANT_ESP32C5,
|
||||||
|
},
|
||||||
"seeed_xiao_esp32c6": {
|
"seeed_xiao_esp32c6": {
|
||||||
"name": "Seeed Studio XIAO ESP32C6",
|
"name": "Seeed Studio XIAO ESP32C6",
|
||||||
"variant": VARIANT_ESP32C6,
|
"variant": VARIANT_ESP32C6,
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ uint32_t arch_get_cpu_freq_hz() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static StackType_t
|
||||||
|
loop_task_stack[ESPHOME_LOOP_TASK_STACK_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
void loop_task(void *pv_params) {
|
void loop_task(void *pv_params) {
|
||||||
setup();
|
setup();
|
||||||
@@ -73,9 +76,11 @@ extern "C" void app_main() {
|
|||||||
initArduino();
|
initArduino();
|
||||||
esp32::setup_preferences();
|
esp32::setup_preferences();
|
||||||
#if CONFIG_FREERTOS_UNICORE
|
#if CONFIG_FREERTOS_UNICORE
|
||||||
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
|
loop_task_handle = xTaskCreateStatic(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack,
|
||||||
|
&loop_task_tcb);
|
||||||
#else
|
#else
|
||||||
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
|
loop_task_handle = xTaskCreateStaticPinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1,
|
||||||
|
loop_task_stack, &loop_task_tcb, 1);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -12,9 +11,6 @@ namespace esphome::esp32 {
|
|||||||
|
|
||||||
static const char *const TAG = "preferences";
|
static const char *const TAG = "preferences";
|
||||||
|
|
||||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
|
||||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
|
||||||
|
|
||||||
struct NVSData {
|
struct NVSData {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||||
@@ -51,8 +47,8 @@ bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
|
uint32_to_str(key_str, this->key);
|
||||||
size_t actual_len;
|
size_t actual_len;
|
||||||
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
|
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
@@ -108,8 +104,8 @@ bool ESP32Preferences::sync() {
|
|||||||
uint32_t last_key = 0;
|
uint32_t last_key = 0;
|
||||||
|
|
||||||
for (const auto &save : s_pending_save) {
|
for (const auto &save : s_pending_save) {
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
uint32_to_str(key_str, save.key);
|
||||||
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
||||||
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
||||||
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
|
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
|
||||||
|
|||||||
@@ -108,8 +108,13 @@ async def globals_set_to_code(config, action_id, template_arg, args):
|
|||||||
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
# Use the global's value_type alias as the lambda return type so
|
||||||
|
# TemplatableFn stores a direct function pointer instead of going through
|
||||||
|
# the deprecated converting trampoline when the value expression deduces
|
||||||
|
# to a different type (e.g. int literal assigned to a float global).
|
||||||
|
value_type = cg.RawExpression(f"{full_id.type}::value_type")
|
||||||
templ = await cg.templatable(
|
templ = await cg.templatable(
|
||||||
config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True
|
config[CONF_VALUE], args, value_type, to_exp=cg.RawExpression
|
||||||
)
|
)
|
||||||
cg.add(var.set_value(templ))
|
cg.add(var.set_value(templ))
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -127,6 +127,6 @@ async def to_code(config):
|
|||||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
cg.add_build_flag("-Wno-error=overloaded-virtual")
|
cg.add_build_flag("-Wno-error=overloaded-virtual")
|
||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.40")
|
cg.add_library("tonia/HeatpumpIR", "1.0.41")
|
||||||
if CORE.is_libretiny or CORE.is_esp32:
|
if CORE.is_libretiny or CORE.is_esp32:
|
||||||
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
||||||
|
|||||||
@@ -360,8 +360,8 @@ void LD2410Component::handle_periodic_data_() {
|
|||||||
*/
|
*/
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
|
SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
|
||||||
encode_uint16(this->buffer_data_[MOVING_TARGET_HIGH], this->buffer_data_[MOVING_TARGET_LOW]))
|
encode_uint16(this->buffer_data_[MOVING_TARGET_HIGH], this->buffer_data_[MOVING_TARGET_LOW]));
|
||||||
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY])
|
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY]);
|
||||||
SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
|
SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
|
||||||
encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]));
|
encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]));
|
||||||
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY]);
|
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY]);
|
||||||
@@ -375,26 +375,26 @@ void LD2410Component::handle_periodic_data_() {
|
|||||||
Moving energy: 20~28th bytes
|
Moving energy: 20~28th bytes
|
||||||
*/
|
*/
|
||||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||||
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i])
|
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i]);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Still energy: 29~37th bytes
|
Still energy: 29~37th bytes
|
||||||
*/
|
*/
|
||||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||||
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i])
|
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i]);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Light sensor: 38th bytes
|
Light sensor: 38th bytes
|
||||||
*/
|
*/
|
||||||
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR])
|
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR]);
|
||||||
} else {
|
} else {
|
||||||
for (auto &gate_move_sensor : this->gate_move_sensors_) {
|
for (auto &gate_move_sensor : this->gate_move_sensors_) {
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor);
|
||||||
}
|
}
|
||||||
for (auto &gate_still_sensor : this->gate_still_sensors_) {
|
for (auto &gate_still_sensor : this->gate_still_sensors_) {
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor);
|
||||||
}
|
}
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@@ -786,13 +786,12 @@ void LD2410Component::set_light_out_control() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// These could leak memory, but they are only set once prior to 'setup()' and should never be used again.
|
|
||||||
void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
|
void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||||
this->gate_move_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
this->gate_move_sensors_[gate].set_sensor(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
|
void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||||
this->gate_still_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
this->gate_still_sensors_[gate].set_sensor(s);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
|||||||
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
|
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_move_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
|
||||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_still_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -397,12 +397,12 @@ void LD2412Component::handle_periodic_data_() {
|
|||||||
*/
|
*/
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
|
SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
|
||||||
encode_uint16(this->buffer_data_[MOVING_TARGET_HIGH], this->buffer_data_[MOVING_TARGET_LOW]))
|
encode_uint16(this->buffer_data_[MOVING_TARGET_HIGH], this->buffer_data_[MOVING_TARGET_LOW]));
|
||||||
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY])
|
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY]);
|
||||||
SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
|
SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
|
||||||
encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]))
|
encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]));
|
||||||
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY])
|
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY]);
|
||||||
if (this->detection_distance_sensor_ != nullptr) {
|
if (this->detection_distance_sensor_.has_sensor()) {
|
||||||
int new_detect_distance = 0;
|
int new_detect_distance = 0;
|
||||||
if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
|
if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
|
||||||
new_detect_distance =
|
new_detect_distance =
|
||||||
@@ -410,7 +410,7 @@ void LD2412Component::handle_periodic_data_() {
|
|||||||
} else if (target_state != 0x00) {
|
} else if (target_state != 0x00) {
|
||||||
new_detect_distance = encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]);
|
new_detect_distance = encode_uint16(this->buffer_data_[STILL_TARGET_HIGH], this->buffer_data_[STILL_TARGET_LOW]);
|
||||||
}
|
}
|
||||||
this->detection_distance_sensor_->publish_state_if_not_dup(new_detect_distance);
|
this->detection_distance_sensor_.publish_state_if_not_dup(new_detect_distance);
|
||||||
}
|
}
|
||||||
if (engineering_mode) {
|
if (engineering_mode) {
|
||||||
// Engineering mode needs at least LIGHT_SENSOR + 1 bytes
|
// Engineering mode needs at least LIGHT_SENSOR + 1 bytes
|
||||||
@@ -423,27 +423,27 @@ void LD2412Component::handle_periodic_data_() {
|
|||||||
Moving energy: 20~28th bytes
|
Moving energy: 20~28th bytes
|
||||||
*/
|
*/
|
||||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||||
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i])
|
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i]);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Still energy: 29~37th bytes
|
Still energy: 29~37th bytes
|
||||||
*/
|
*/
|
||||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||||
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i])
|
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i]);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Light sensor value
|
Light sensor value
|
||||||
*/
|
*/
|
||||||
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR])
|
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (auto &gate_move_sensor : this->gate_move_sensors_) {
|
for (auto &gate_move_sensor : this->gate_move_sensors_) {
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor);
|
||||||
}
|
}
|
||||||
for (auto &gate_still_sensor : this->gate_still_sensors_) {
|
for (auto &gate_still_sensor : this->gate_still_sensors_) {
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor);
|
||||||
}
|
}
|
||||||
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_)
|
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// the radar module won't tell us when it's done, so we just have to keep polling...
|
// the radar module won't tell us when it's done, so we just have to keep polling...
|
||||||
@@ -846,12 +846,11 @@ void LD2412Component::set_light_out_control() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// These could leak memory, but they are only set once prior to 'setup()' and should never be used again.
|
|
||||||
void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
|
void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||||
this->gate_move_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
this->gate_move_sensors_[gate].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
|
void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||||
this->gate_still_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
this->gate_still_sensors_[gate].set_sensor(s);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ class LD2412Component : public Component, public uart::UARTDevice {
|
|||||||
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
|
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_move_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_move_sensors_{};
|
||||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_still_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, TOTAL_GATES> gate_still_sensors_{};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -565,6 +565,7 @@ void LD2450Component::handle_periodic_data_() {
|
|||||||
SAFE_PUBLISH_SENSOR(this->still_target_count_sensor_, still_target_count);
|
SAFE_PUBLISH_SENSOR(this->still_target_count_sensor_, still_target_count);
|
||||||
// Moving Target Count
|
// Moving Target Count
|
||||||
SAFE_PUBLISH_SENSOR(this->moving_target_count_sensor_, moving_target_count);
|
SAFE_PUBLISH_SENSOR(this->moving_target_count_sensor_, moving_target_count);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@@ -872,33 +873,32 @@ void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QU
|
|||||||
void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); }
|
void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); }
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// These could leak memory, but they are only set once prior to 'setup()' and should never be used again.
|
|
||||||
void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_x_sensors_[target] = new SensorWithDedup<int16_t>(s);
|
this->move_x_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_y_sensors_[target] = new SensorWithDedup<int16_t>(s);
|
this->move_y_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_speed_sensors_[target] = new SensorWithDedup<int16_t>(s);
|
this->move_speed_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_angle_sensors_[target] = new SensorWithDedup<float>(s);
|
this->move_angle_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_distance_sensors_[target] = new SensorWithDedup<uint16_t>(s);
|
this->move_distance_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
|
void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
|
||||||
this->move_resolution_sensors_[target] = new SensorWithDedup<uint16_t>(s);
|
this->move_resolution_sensors_[target].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||||
this->zone_target_count_sensors_[zone] = new SensorWithDedup<uint8_t>(s);
|
this->zone_target_count_sensors_[zone].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||||
this->zone_still_target_count_sensors_[zone] = new SensorWithDedup<uint8_t>(s);
|
this->zone_still_target_count_sensors_[zone].set_sensor(s);
|
||||||
}
|
}
|
||||||
void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||||
this->zone_moving_target_count_sensors_[zone] = new SensorWithDedup<uint8_t>(s);
|
this->zone_moving_target_count_sensors_[zone].set_sensor(s);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
|||||||
@@ -182,15 +182,15 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
|||||||
ZoneOfNumbers zone_numbers_[MAX_ZONES];
|
ZoneOfNumbers zone_numbers_[MAX_ZONES];
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
std::array<SensorWithDedup<int16_t> *, MAX_TARGETS> move_x_sensors_{};
|
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_x_sensors_{};
|
||||||
std::array<SensorWithDedup<int16_t> *, MAX_TARGETS> move_y_sensors_{};
|
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_y_sensors_{};
|
||||||
std::array<SensorWithDedup<int16_t> *, MAX_TARGETS> move_speed_sensors_{};
|
std::array<SensorWithDedup<int16_t>, MAX_TARGETS> move_speed_sensors_{};
|
||||||
std::array<SensorWithDedup<float> *, MAX_TARGETS> move_angle_sensors_{};
|
std::array<SensorWithDedup<float>, MAX_TARGETS> move_angle_sensors_{};
|
||||||
std::array<SensorWithDedup<uint16_t> *, MAX_TARGETS> move_distance_sensors_{};
|
std::array<SensorWithDedup<uint16_t>, MAX_TARGETS> move_distance_sensors_{};
|
||||||
std::array<SensorWithDedup<uint16_t> *, MAX_TARGETS> move_resolution_sensors_{};
|
std::array<SensorWithDedup<uint16_t>, MAX_TARGETS> move_resolution_sensors_{};
|
||||||
std::array<SensorWithDedup<uint8_t> *, MAX_ZONES> zone_target_count_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_target_count_sensors_{};
|
||||||
std::array<SensorWithDedup<uint8_t> *, MAX_ZONES> zone_still_target_count_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_still_target_count_sensors_{};
|
||||||
std::array<SensorWithDedup<uint8_t> *, MAX_ZONES> zone_moving_target_count_sensors_{};
|
std::array<SensorWithDedup<uint8_t>, MAX_ZONES> zone_moving_target_count_sensors_{};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
std::array<text_sensor::TextSensor *, MAX_TARGETS> direction_text_sensors_{};
|
std::array<text_sensor::TextSensor *, MAX_TARGETS> direction_text_sensors_{};
|
||||||
|
|||||||
@@ -11,28 +11,20 @@
|
|||||||
|
|
||||||
#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
|
#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
|
||||||
protected: \
|
protected: \
|
||||||
ld24xx::SensorWithDedup<dedup_type> *name##_sensor_{nullptr}; \
|
ld24xx::SensorWithDedup<dedup_type> name##_sensor_{}; \
|
||||||
\
|
\
|
||||||
public: \
|
public: \
|
||||||
void set_##name##_sensor(sensor::Sensor *sensor) { \
|
void set_##name##_sensor(sensor::Sensor *sensor) { this->name##_sensor_.set_sensor(sensor); }
|
||||||
this->name##_sensor_ = new ld24xx::SensorWithDedup<dedup_type>(sensor); \
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define LOG_SENSOR_WITH_DEDUP_SAFE(tag, name, sensor) \
|
#define LOG_SENSOR_WITH_DEDUP_SAFE(tag, name, sensor) \
|
||||||
if ((sensor) != nullptr) { \
|
if ((sensor).has_sensor()) { \
|
||||||
LOG_SENSOR(tag, name, (sensor)->sens); \
|
LOG_SENSOR(tag, name, (sensor).get_sensor()); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SAFE_PUBLISH_SENSOR(sensor, value) \
|
#define SAFE_PUBLISH_SENSOR(sensor, value) (sensor).publish_state_if_not_dup(value)
|
||||||
if ((sensor) != nullptr) { \
|
|
||||||
(sensor)->publish_state_if_not_dup(value); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SAFE_PUBLISH_SENSOR_UNKNOWN(sensor) \
|
#define SAFE_PUBLISH_SENSOR_UNKNOWN(sensor) (sensor).publish_state_unknown()
|
||||||
if ((sensor) != nullptr) { \
|
|
||||||
(sensor)->publish_state_unknown(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||||
@@ -70,25 +62,33 @@ inline void format_version_str(const uint8_t *version, std::span<char, 20> buffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
// Helper class to store a sensor with a deduplicator & publish state only when the value changes
|
/// Sensor with deduplication — sensor may be null, null check is internal.
|
||||||
|
/// Stored inline, no heap allocation. Does nothing when no sensor is set.
|
||||||
template<typename T> class SensorWithDedup {
|
template<typename T> class SensorWithDedup {
|
||||||
public:
|
public:
|
||||||
SensorWithDedup(sensor::Sensor *sens) : sens(sens) {}
|
void set_sensor(sensor::Sensor *sens) {
|
||||||
|
this->sens_ = sens;
|
||||||
|
this->dedup_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
void publish_state_if_not_dup(T state) {
|
void publish_state_if_not_dup(T state) {
|
||||||
if (this->publish_dedup.next(state)) {
|
if (this->sens_ != nullptr && this->dedup_.next(state)) {
|
||||||
this->sens->publish_state(static_cast<float>(state));
|
this->sens_->publish_state(static_cast<float>(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void publish_state_unknown() {
|
void publish_state_unknown() {
|
||||||
if (this->publish_dedup.next_unknown()) {
|
if (this->sens_ != nullptr && this->dedup_.next_unknown()) {
|
||||||
this->sens->publish_state(NAN);
|
this->sens_->publish_state(NAN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sensor::Sensor *sens;
|
bool has_sensor() const { return this->sens_ != nullptr; }
|
||||||
Deduplicator<T> publish_dedup;
|
sensor::Sensor *get_sensor() const { return this->sens_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *sens_{nullptr};
|
||||||
|
Deduplicator<T> dedup_;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
} // namespace esphome::ld24xx
|
} // namespace esphome::ld24xx
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -11,9 +10,6 @@ namespace esphome::libretiny {
|
|||||||
|
|
||||||
static const char *const TAG = "preferences";
|
static const char *const TAG = "preferences";
|
||||||
|
|
||||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
|
||||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
|
||||||
|
|
||||||
struct NVSData {
|
struct NVSData {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||||
@@ -50,8 +46,8 @@ bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
|
uint32_to_str(key_str, this->key);
|
||||||
fdb_blob_make(this->blob, data, len);
|
fdb_blob_make(this->blob, data, len);
|
||||||
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
|
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
|
||||||
if (actual_len != len) {
|
if (actual_len != len) {
|
||||||
@@ -92,8 +88,8 @@ bool LibreTinyPreferences::sync() {
|
|||||||
uint32_t last_key = 0;
|
uint32_t last_key = 0;
|
||||||
|
|
||||||
for (const auto &save : s_pending_save) {
|
for (const auto &save : s_pending_save) {
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
uint32_to_str(key_str, save.key);
|
||||||
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
|
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
|
||||||
if (this->is_changed_(&this->db, save, key_str)) {
|
if (this->is_changed_(&this->db, save, key_str)) {
|
||||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
||||||
|
|||||||
@@ -22,4 +22,20 @@ uint8_t ESPColorCorrection::gamma_uncorrect_(uint8_t value) const {
|
|||||||
return (target - a <= b - target) ? lo : lo + 1;
|
return (target - a <= b - target) ? lo : lo + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color ESPColorCorrection::color_uncorrect(Color color) const {
|
||||||
|
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
||||||
|
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
|
||||||
|
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ESPColorCorrection::color_uncorrect_channel_(uint8_t value, uint8_t max_brightness) const {
|
||||||
|
if (max_brightness == 0 || this->local_brightness_ == 0)
|
||||||
|
return 0;
|
||||||
|
// Use 32-bit intermediates: when max_brightness and local_brightness_ are small but non-zero,
|
||||||
|
// (uncorrected / max_brightness) * 255 can exceed 65535 before the std::min(255) clamp runs.
|
||||||
|
uint32_t uncorrected = this->gamma_uncorrect_(value) * 255UL;
|
||||||
|
uint32_t res = ((uncorrected / max_brightness) * 255UL) / this->local_brightness_;
|
||||||
|
return static_cast<uint8_t>(std::min(res, uint32_t(255)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome::light
|
} // namespace esphome::light
|
||||||
|
|||||||
@@ -46,38 +46,18 @@ class ESPColorCorrection {
|
|||||||
uint8_t res = esp_scale8_twice(white, this->max_brightness_.white, this->local_brightness_);
|
uint8_t res = esp_scale8_twice(white, this->max_brightness_.white, this->local_brightness_);
|
||||||
return this->gamma_correct_(res);
|
return this->gamma_correct_(res);
|
||||||
}
|
}
|
||||||
inline Color color_uncorrect(Color color) const ESPHOME_ALWAYS_INLINE {
|
Color color_uncorrect(Color color) const;
|
||||||
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
|
||||||
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
|
|
||||||
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
|
|
||||||
}
|
|
||||||
inline uint8_t color_uncorrect_red(uint8_t red) const ESPHOME_ALWAYS_INLINE {
|
inline uint8_t color_uncorrect_red(uint8_t red) const ESPHOME_ALWAYS_INLINE {
|
||||||
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
|
return this->color_uncorrect_channel_(red, this->max_brightness_.red);
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_uncorrect_(red) * 255UL;
|
|
||||||
uint16_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
|
|
||||||
return (uint8_t) std::min(res, uint16_t(255));
|
|
||||||
}
|
}
|
||||||
inline uint8_t color_uncorrect_green(uint8_t green) const ESPHOME_ALWAYS_INLINE {
|
inline uint8_t color_uncorrect_green(uint8_t green) const ESPHOME_ALWAYS_INLINE {
|
||||||
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
|
return this->color_uncorrect_channel_(green, this->max_brightness_.green);
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_uncorrect_(green) * 255UL;
|
|
||||||
uint16_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
|
|
||||||
return (uint8_t) std::min(res, uint16_t(255));
|
|
||||||
}
|
}
|
||||||
inline uint8_t color_uncorrect_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE {
|
inline uint8_t color_uncorrect_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE {
|
||||||
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
|
return this->color_uncorrect_channel_(blue, this->max_brightness_.blue);
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_uncorrect_(blue) * 255UL;
|
|
||||||
uint16_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
|
|
||||||
return (uint8_t) std::min(res, uint16_t(255));
|
|
||||||
}
|
}
|
||||||
inline uint8_t color_uncorrect_white(uint8_t white) const ESPHOME_ALWAYS_INLINE {
|
inline uint8_t color_uncorrect_white(uint8_t white) const ESPHOME_ALWAYS_INLINE {
|
||||||
if (this->max_brightness_.white == 0 || this->local_brightness_ == 0)
|
return this->color_uncorrect_channel_(white, this->max_brightness_.white);
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_uncorrect_(white) * 255UL;
|
|
||||||
uint16_t res = ((uncorrected / this->max_brightness_.white) * 255UL) / this->local_brightness_;
|
|
||||||
return (uint8_t) std::min(res, uint16_t(255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -85,6 +65,9 @@ class ESPColorCorrection {
|
|||||||
uint8_t gamma_correct_(uint8_t value) const;
|
uint8_t gamma_correct_(uint8_t value) const;
|
||||||
/// Reverse gamma: binary search the forward PROGMEM table
|
/// Reverse gamma: binary search the forward PROGMEM table
|
||||||
uint8_t gamma_uncorrect_(uint8_t value) const;
|
uint8_t gamma_uncorrect_(uint8_t value) const;
|
||||||
|
/// Shared body of color_uncorrect_{red,green,blue,white}. Kept out-of-line
|
||||||
|
/// to avoid duplicating two 16-bit divides at every call site.
|
||||||
|
uint8_t color_uncorrect_channel_(uint8_t value, uint8_t max_brightness) const;
|
||||||
|
|
||||||
const uint16_t *gamma_table_{nullptr};
|
const uint16_t *gamma_table_{nullptr};
|
||||||
Color max_brightness_{255, 255, 255, 255};
|
Color max_brightness_{255, 255, 255, 255};
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ async def to_code(configs):
|
|||||||
df.LOGGER.info("LVGL will use hardware rotation via display driver")
|
df.LOGGER.info("LVGL will use hardware rotation via display driver")
|
||||||
else:
|
else:
|
||||||
rotation_type = RotationType.ROTATION_SOFTWARE
|
rotation_type = RotationType.ROTATION_SOFTWARE
|
||||||
if get_esp32_variant() == VARIANT_ESP32P4:
|
if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32P4:
|
||||||
df.LOGGER.info("LVGL will use software rotation (PPA accelerated)")
|
df.LOGGER.info("LVGL will use software rotation (PPA accelerated)")
|
||||||
else:
|
else:
|
||||||
df.LOGGER.info("LVGL will use software rotation")
|
df.LOGGER.info("LVGL will use software rotation")
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ async def to_code(config):
|
|||||||
cg.add_library("LEAmDNS", None)
|
cg.add_library("LEAmDNS", None)
|
||||||
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
add_idf_component(name="espressif/mdns", ref="1.10.0")
|
add_idf_component(name="espressif/mdns", ref="1.11.0")
|
||||||
|
|
||||||
cg.add_define("USE_MDNS")
|
cg.add_define("USE_MDNS")
|
||||||
|
|
||||||
|
|||||||
@@ -452,7 +452,7 @@ async def to_code(config):
|
|||||||
|
|
||||||
esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1")
|
esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1")
|
||||||
# Pin esp-nn for stable future builds (esp-tflite-micro depends on esp-nn)
|
# Pin esp-nn for stable future builds (esp-tflite-micro depends on esp-nn)
|
||||||
esp32.add_idf_component(name="espressif/esp-nn", ref="1.2.1")
|
esp32.add_idf_component(name="espressif/esp-nn", ref="1.1.2")
|
||||||
|
|
||||||
cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
|
cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
|
||||||
cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
|
cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ void AirConditioner::on_status_change() {
|
|||||||
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK &&
|
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK &&
|
||||||
this->base_.getCapabilities().supportFrostProtectionPreset() && !this->frost_protection_set_) {
|
this->base_.getCapabilities().supportFrostProtectionPreset() && !this->frost_protection_set_) {
|
||||||
// Read existing presets (set by codegen), append frost protection, write back
|
// Read existing presets (set by codegen), append frost protection, write back
|
||||||
const auto &existing = this->get_traits().get_supported_custom_presets();
|
auto traits = this->get_traits();
|
||||||
|
const auto &existing = traits.get_supported_custom_presets();
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (const char *p : existing) {
|
for (const char *p : existing) {
|
||||||
if (strcmp(p, Constants::FREEZE_PROTECTION) == 0) {
|
if (strcmp(p, Constants::FREEZE_PROTECTION) == 0) {
|
||||||
|
|||||||
@@ -234,9 +234,9 @@ class MipiSpi : public display::Display,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dump_config() override {
|
void dump_config() override {
|
||||||
internal_dump_config(this->model_, this->get_width(), this->get_height(), OFFSET_WIDTH, OFFSET_HEIGHT, MADCTL,
|
internal_dump_config(this->model_, this->get_width(), this->get_height(), OFFSET_WIDTH, OFFSET_HEIGHT,
|
||||||
this->invert_colors_, DISPLAYPIXEL * 8, IS_BIG_ENDIAN, this->brightness_, this->cs_,
|
(uint8_t) MADCTL, this->invert_colors_, DISPLAYPIXEL * 8, IS_BIG_ENDIAN, this->brightness_,
|
||||||
this->reset_pin_, this->dc_pin_, this->mode_, this->data_rate_, BUS_TYPE,
|
this->cs_, this->reset_pin_, this->dc_pin_, this->mode_, this->data_rate_, BUS_TYPE,
|
||||||
HAS_HARDWARE_ROTATION);
|
HAS_HARDWARE_ROTATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ class MipiSpi : public display::Display,
|
|||||||
this->write_command_(BRIGHTNESS, this->brightness_.value());
|
this->write_command_(BRIGHTNESS, this->brightness_.value());
|
||||||
|
|
||||||
// calculate new madctl value from base value adjusted for rotation
|
// calculate new madctl value from base value adjusted for rotation
|
||||||
uint8_t madctl = MADCTL; // lower 8 bits only
|
uint8_t madctl = (uint8_t) MADCTL; // lower 8 bits only
|
||||||
constexpr bool use_flips = (MADCTL & MADCTL_FLIP_FLAG) != 0;
|
constexpr bool use_flips = (MADCTL & MADCTL_FLIP_FLAG) != 0;
|
||||||
constexpr uint8_t x_mask = use_flips ? MADCTL_XFLIP : MADCTL_MX;
|
constexpr uint8_t x_mask = use_flips ? MADCTL_XFLIP : MADCTL_MX;
|
||||||
constexpr uint8_t y_mask = use_flips ? MADCTL_YFLIP : MADCTL_MY;
|
constexpr uint8_t y_mask = use_flips ? MADCTL_YFLIP : MADCTL_MY;
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ def is_remote_package(package_config: dict) -> bool:
|
|||||||
return CONF_URL in package_config
|
return CONF_URL in package_config
|
||||||
|
|
||||||
|
|
||||||
|
def is_package_definition(value: object) -> bool:
|
||||||
|
"""Returns True if the value looks like a package definition rather than a config fragment.
|
||||||
|
|
||||||
|
Package definitions are IncludeFile objects, git URL shorthand strings, or
|
||||||
|
remote package dicts (containing a ``url:`` key). Config fragments are
|
||||||
|
plain dicts that represent component configuration.
|
||||||
|
"""
|
||||||
|
return isinstance(value, (yaml_util.IncludeFile, str)) or (
|
||||||
|
isinstance(value, dict) and is_remote_package(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def valid_package_contents(package_config: dict) -> dict:
|
def valid_package_contents(package_config: dict) -> dict:
|
||||||
"""Validate that a package looks like a plausible ESPHome config fragment.
|
"""Validate that a package looks like a plausible ESPHome config fragment.
|
||||||
|
|
||||||
@@ -309,20 +321,23 @@ def _walk_packages(
|
|||||||
return config
|
return config
|
||||||
packages = config[CONF_PACKAGES]
|
packages = config[CONF_PACKAGES]
|
||||||
|
|
||||||
|
with cv.prepend_path(CONF_PACKAGES):
|
||||||
|
if isinstance(packages, yaml_util.IncludeFile):
|
||||||
|
# If the packages key is an IncludeFile, resolve it first before processing.
|
||||||
|
packages, _ = resolve_include(packages, [], context, strict_undefined=False)
|
||||||
if not isinstance(packages, (dict, list)):
|
if not isinstance(packages, (dict, list)):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Packages must be a key to value mapping or list, got {type(packages)} instead"
|
f"Packages must be a key to value mapping or list, got {type(packages)} instead"
|
||||||
)
|
)
|
||||||
|
|
||||||
with cv.prepend_path(CONF_PACKAGES):
|
|
||||||
if not isinstance(packages, dict):
|
if not isinstance(packages, dict):
|
||||||
_walk_package_list(packages, callback, context)
|
_walk_package_list(packages, callback, context)
|
||||||
elif (result := _walk_package_dict(packages, callback, context)) is not None:
|
elif (result := _walk_package_dict(packages, callback, context)) is not None:
|
||||||
if not validate_deprecated:
|
if not validate_deprecated or any(
|
||||||
|
is_package_definition(v) for v in packages.values()
|
||||||
|
):
|
||||||
raise result
|
raise result
|
||||||
# Fallback: treat the dict as a single deprecated package.
|
# Fallback: treat the dict as a single deprecated package.
|
||||||
# Note: this catches *any* cv.Invalid from the callback, which may
|
|
||||||
# mask real validation errors in named package dicts.
|
|
||||||
# This block can be removed once the single-package
|
# This block can be removed once the single-package
|
||||||
# deprecation period (2026.7.0) is over.
|
# deprecation period (2026.7.0) is over.
|
||||||
config[CONF_PACKAGES] = [packages]
|
config[CONF_PACKAGES] = [packages]
|
||||||
@@ -461,6 +476,9 @@ class _PackageProcessor:
|
|||||||
self, package_config: dict | str, context_vars: ContextVars | None
|
self, package_config: dict | str, context_vars: ContextVars | None
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Resolve a single package and recurse into any nested packages."""
|
"""Resolve a single package and recurse into any nested packages."""
|
||||||
|
from_remote = isinstance(package_config, dict) and is_remote_package(
|
||||||
|
package_config
|
||||||
|
)
|
||||||
package_config = self.resolve_package(package_config, context_vars)
|
package_config = self.resolve_package(package_config, context_vars)
|
||||||
self.collect_substitutions(package_config)
|
self.collect_substitutions(package_config)
|
||||||
|
|
||||||
@@ -470,7 +488,18 @@ class _PackageProcessor:
|
|||||||
# Push context from !include vars on the package root and on the packages key
|
# Push context from !include vars on the package root and on the packages key
|
||||||
context_vars = push_context(package_config, context_vars)
|
context_vars = push_context(package_config, context_vars)
|
||||||
context_vars = push_context(package_config[CONF_PACKAGES], context_vars)
|
context_vars = push_context(package_config[CONF_PACKAGES], context_vars)
|
||||||
return _walk_packages(package_config, self.process_package, context_vars)
|
# Disable the deprecated single-package fallback for remote
|
||||||
|
# packages. _process_remote_package returns dicts with
|
||||||
|
# already-resolved values that is_package_definition cannot
|
||||||
|
# distinguish from config fragments, so the fallback would
|
||||||
|
# always fire and mask real errors with wrong paths
|
||||||
|
# (packages->0 instead of packages-><name>).
|
||||||
|
return _walk_packages(
|
||||||
|
package_config,
|
||||||
|
self.process_package,
|
||||||
|
context_vars,
|
||||||
|
validate_deprecated=not from_remote,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def do_packages_pass(
|
def do_packages_pass(
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration
|
|||||||
my_integration_time_regval = integration_time;
|
my_integration_time_regval = integration_time;
|
||||||
this->integration_time_auto_ = false;
|
this->integration_time_auto_ = false;
|
||||||
}
|
}
|
||||||
this->integration_time_ = (256.f - my_integration_time_regval) * 2.4f;
|
this->integration_time_ = (256.f - (float) my_integration_time_regval) * 2.4f;
|
||||||
ESP_LOGI(TAG, "TCS34725I Integration time set to: %.1fms", this->integration_time_);
|
ESP_LOGI(TAG, "TCS34725I Integration time set to: %.1fms", this->integration_time_);
|
||||||
}
|
}
|
||||||
void TCS34725Component::set_gain(TCS34725Gain gain) {
|
void TCS34725Component::set_gain(TCS34725Gain gain) {
|
||||||
|
|||||||
@@ -114,7 +114,25 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf
|
|||||||
uint8_t *data, size_t len, bool final) {
|
uint8_t *data, size_t len, bool final) {
|
||||||
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
|
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
|
||||||
|
|
||||||
if (index == 0 && !this->ota_backend_) {
|
// First byte of a new upload: index==0 with actual data. (web_server_idf
|
||||||
|
// fires a separate start-marker call with data==nullptr/len==0 before the
|
||||||
|
// first real chunk; gate on len>0 so we only trigger once per upload.)
|
||||||
|
if (index == 0 && len > 0) {
|
||||||
|
// If a previous upload was interrupted (e.g. client closed the tab, TCP
|
||||||
|
// reset) the backend from that session may still be open. Tear it down
|
||||||
|
// so flash state doesn't get concatenated with the new image (which can
|
||||||
|
// produce a technically-valid-sized but corrupted firmware that bricks
|
||||||
|
// the device once it reboots).
|
||||||
|
if (this->ota_backend_) {
|
||||||
|
ESP_LOGW(TAG, "New OTA upload received while previous session was still open; aborting previous session");
|
||||||
|
this->ota_backend_->abort();
|
||||||
|
#ifdef USE_OTA_STATE_LISTENER
|
||||||
|
// Notify listeners that the previous session was aborted before the new one starts.
|
||||||
|
this->parent_->notify_state_deferred_(ota::OTA_ABORT, 0.0f, 0);
|
||||||
|
#endif
|
||||||
|
this->ota_backend_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize OTA on first call
|
// Initialize OTA on first call
|
||||||
this->ota_init_(filename.c_str());
|
this->ota_init_(filename.c_str());
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ int main() {
|
|||||||
setup();
|
setup();
|
||||||
while (true) {
|
while (true) {
|
||||||
loop();
|
loop();
|
||||||
esphome::yield();
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,12 @@ void Application::setup() {
|
|||||||
if (component->can_proceed())
|
if (component->can_proceed())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Force the status LED to blink WARNING while we wait for a slow
|
||||||
|
// component to come up. Cleared after setup() finishes if no real
|
||||||
|
// component has warning set.
|
||||||
|
this->app_state_ |= STATUS_LED_WARNING;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
// Process pending loop enables to handle GPIO interrupts during setup
|
// Process pending loop enables to handle GPIO interrupts during setup
|
||||||
@@ -96,17 +100,26 @@ void Application::setup() {
|
|||||||
// Update loop_component_start_time_ right before calling each component
|
// Update loop_component_start_time_ right before calling each component
|
||||||
this->loop_component_start_time_ = millis();
|
this->loop_component_start_time_ = millis();
|
||||||
this->components_[j]->call();
|
this->components_[j]->call();
|
||||||
new_app_state |= this->components_[j]->get_component_state();
|
|
||||||
this->app_state_ |= new_app_state;
|
|
||||||
this->feed_wdt();
|
this->feed_wdt();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->after_loop_tasks_();
|
this->after_loop_tasks_();
|
||||||
this->app_state_ = new_app_state;
|
|
||||||
yield();
|
yield();
|
||||||
} while (!component->can_proceed() && !component->is_failed());
|
} while (!component->can_proceed() && !component->is_failed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup is complete. Reconcile STATUS_LED_WARNING: the slow-setup path
|
||||||
|
// above may have forced it on, and any status_clear_warning() calls
|
||||||
|
// from components during setup were intentional no-ops (gated by
|
||||||
|
// APP_STATE_SETUP_COMPLETE). Walk components once here to pick up the
|
||||||
|
// real state. STATUS_LED_ERROR is never artificially forced, so its
|
||||||
|
// clear path always works and needs no reconciliation. Finally, set
|
||||||
|
// APP_STATE_SETUP_COMPLETE so subsequent warning clears go through
|
||||||
|
// the normal walk-and-clear path.
|
||||||
|
if (!this->any_component_has_status_flag_(STATUS_LED_WARNING))
|
||||||
|
this->app_state_ &= ~STATUS_LED_WARNING;
|
||||||
|
this->app_state_ |= APP_STATE_SETUP_COMPLETE;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "setup() finished successfully!");
|
ESP_LOGI(TAG, "setup() finished successfully!");
|
||||||
|
|
||||||
#ifdef USE_SETUP_PRIORITY_OVERRIDE
|
#ifdef USE_SETUP_PRIORITY_OVERRIDE
|
||||||
@@ -216,6 +229,20 @@ void HOT Application::feed_wdt_slow_(uint32_t time) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::any_component_has_status_flag_(uint8_t flag) const {
|
||||||
|
// Walk all components (not just looping ones) so non-looping components'
|
||||||
|
// status bits are respected. Only called from the slow-path clear helpers
|
||||||
|
// (status_clear_warning_slow_path_ / status_clear_error_slow_path_) on an
|
||||||
|
// actual set→clear transition, so walking O(N) here is paid once per
|
||||||
|
// transition — not once per loop iteration.
|
||||||
|
for (auto *component : this->components_) {
|
||||||
|
if ((component->get_component_state() & flag) != 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::reboot() {
|
void Application::reboot() {
|
||||||
ESP_LOGI(TAG, "Forcing a reboot");
|
ESP_LOGI(TAG, "Forcing a reboot");
|
||||||
for (auto &component : std::ranges::reverse_view(this->components_)) {
|
for (auto &component : std::ranges::reverse_view(this->components_)) {
|
||||||
|
|||||||
@@ -418,7 +418,18 @@ class Application {
|
|||||||
*/
|
*/
|
||||||
void teardown_components(uint32_t timeout_ms);
|
void teardown_components(uint32_t timeout_ms);
|
||||||
|
|
||||||
uint8_t get_app_state() const { return this->app_state_; }
|
/// Return the public app state status bits (STATUS_LED_* only).
|
||||||
|
/// Internal bookkeeping bits like APP_STATE_SETUP_COMPLETE are masked
|
||||||
|
/// out so external readers (status_led components, etc.) never see them.
|
||||||
|
uint8_t get_app_state() const { return this->app_state_ & ~APP_STATE_SETUP_COMPLETE; }
|
||||||
|
|
||||||
|
/// True once Application::setup() has finished walking all components
|
||||||
|
/// and finalized the initial status flags. Before this point, the
|
||||||
|
/// slow-setup busy-wait may be forcing STATUS_LED_WARNING on, and
|
||||||
|
/// status_clear_* intentionally skips its walk-and-clear step so the
|
||||||
|
/// forced bit doesn't get wiped. Stored as a free bit on app_state_
|
||||||
|
/// (bit 6) to avoid costing additional RAM.
|
||||||
|
bool is_setup_complete() const { return (this->app_state_ & APP_STATE_SETUP_COMPLETE) != 0; }
|
||||||
|
|
||||||
// Helper macro for entity getter method declarations
|
// Helper macro for entity getter method declarations
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
@@ -594,6 +605,12 @@ class Application {
|
|||||||
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
|
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Walk all registered components looking for any whose component_state_
|
||||||
|
/// has the given flag set. Used by Component::status_clear_*_slow_path_()
|
||||||
|
/// (which is a friend) to decide whether to clear the corresponding bit on
|
||||||
|
/// this->app_state_ (the app-wide "any component has this status" indicator).
|
||||||
|
bool any_component_has_status_flag_(uint8_t flag) const;
|
||||||
|
|
||||||
/// Register a component, detecting loop() override at compile time.
|
/// Register a component, detecting loop() override at compile time.
|
||||||
/// Uses HasLoopOverride<T> which handles ambiguous &T::loop from multiple inheritance.
|
/// Uses HasLoopOverride<T> which handles ambiguous &T::loop from multiple inheritance.
|
||||||
template<typename T> void register_component_(T *comp) {
|
template<typename T> void register_component_(T *comp) {
|
||||||
@@ -860,8 +877,6 @@ inline void ESPHOME_ALWAYS_INLINE Application::before_loop_tasks_(uint32_t loop_
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void ESPHOME_ALWAYS_INLINE Application::loop() {
|
inline void ESPHOME_ALWAYS_INLINE Application::loop() {
|
||||||
uint8_t new_app_state = 0;
|
|
||||||
|
|
||||||
// Get the initial loop time at the start
|
// Get the initial loop time at the start
|
||||||
uint32_t last_op_end_time = millis();
|
uint32_t last_op_end_time = millis();
|
||||||
|
|
||||||
@@ -881,13 +896,10 @@ inline void ESPHOME_ALWAYS_INLINE Application::loop() {
|
|||||||
// Use the finish method to get the current time as the end time
|
// Use the finish method to get the current time as the end time
|
||||||
last_op_end_time = guard.finish();
|
last_op_end_time = guard.finish();
|
||||||
}
|
}
|
||||||
new_app_state |= component->get_component_state();
|
|
||||||
this->app_state_ |= new_app_state;
|
|
||||||
this->feed_wdt_with_time(last_op_end_time);
|
this->feed_wdt_with_time(last_op_end_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->after_loop_tasks_();
|
this->after_loop_tasks_();
|
||||||
this->app_state_ = new_app_state;
|
|
||||||
|
|
||||||
#ifdef USE_RUNTIME_STATS
|
#ifdef USE_RUNTIME_STATS
|
||||||
// Process any pending runtime stats printing after all components have run
|
// Process any pending runtime stats printing after all components have run
|
||||||
|
|||||||
@@ -411,10 +411,23 @@ void Component::status_set_error(const LogString *message) {
|
|||||||
}
|
}
|
||||||
void Component::status_clear_warning_slow_path_() {
|
void Component::status_clear_warning_slow_path_() {
|
||||||
this->component_state_ &= ~STATUS_LED_WARNING;
|
this->component_state_ &= ~STATUS_LED_WARNING;
|
||||||
|
// Clear the app-wide STATUS_LED_WARNING bit only if setup has finished
|
||||||
|
// AND no other component still has it set. During setup the forced
|
||||||
|
// STATUS_LED_WARNING (from the slow-setup busy-wait) must not be wiped
|
||||||
|
// by a transient component clear — Application::setup() reconciles
|
||||||
|
// the warning bit once at the end before setting APP_STATE_SETUP_COMPLETE.
|
||||||
|
// The set path is unchanged (set_status_flag_ still writes directly).
|
||||||
|
if (App.is_setup_complete() && !App.any_component_has_status_flag_(STATUS_LED_WARNING))
|
||||||
|
App.app_state_ &= ~STATUS_LED_WARNING;
|
||||||
ESP_LOGW(TAG, "%s cleared Warning flag", LOG_STR_ARG(this->get_component_log_str()));
|
ESP_LOGW(TAG, "%s cleared Warning flag", LOG_STR_ARG(this->get_component_log_str()));
|
||||||
}
|
}
|
||||||
void Component::status_clear_error_slow_path_() {
|
void Component::status_clear_error_slow_path_() {
|
||||||
this->component_state_ &= ~STATUS_LED_ERROR;
|
this->component_state_ &= ~STATUS_LED_ERROR;
|
||||||
|
// STATUS_LED_ERROR is never artificially forced — it only ever lands
|
||||||
|
// in app_state_ via a real set_status_flag_ call. So the walk-and-clear
|
||||||
|
// path is always safe, including during setup.
|
||||||
|
if (!App.any_component_has_status_flag_(STATUS_LED_ERROR))
|
||||||
|
App.app_state_ &= ~STATUS_LED_ERROR;
|
||||||
ESP_LOGE(TAG, "%s cleared Error flag", LOG_STR_ARG(this->get_component_log_str()));
|
ESP_LOGE(TAG, "%s cleared Error flag", LOG_STR_ARG(this->get_component_log_str()));
|
||||||
}
|
}
|
||||||
void Component::status_momentary_warning(const char *name, uint32_t length) {
|
void Component::status_momentary_warning(const char *name, uint32_t length) {
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ inline constexpr uint8_t STATUS_LED_WARNING = 0x08;
|
|||||||
inline constexpr uint8_t STATUS_LED_ERROR = 0x10;
|
inline constexpr uint8_t STATUS_LED_ERROR = 0x10;
|
||||||
// Component loop override flag uses bit 5 (set at registration time)
|
// Component loop override flag uses bit 5 (set at registration time)
|
||||||
inline constexpr uint8_t COMPONENT_HAS_LOOP = 0x20;
|
inline constexpr uint8_t COMPONENT_HAS_LOOP = 0x20;
|
||||||
|
// Bit 6 on Application::app_state_ (ONLY) — set at the end of
|
||||||
|
// Application::setup(). Component::status_clear_*_slow_path_() uses this to
|
||||||
|
// decide whether to propagate clears to App.app_state_. Never set on a
|
||||||
|
// Component's component_state_.
|
||||||
|
inline constexpr uint8_t APP_STATE_SETUP_COMPLETE = 0x40;
|
||||||
// Remove before 2026.8.0
|
// Remove before 2026.8.0
|
||||||
enum class RetryResult { DONE, RETRY };
|
enum class RetryResult { DONE, RETRY };
|
||||||
|
|
||||||
|
|||||||
@@ -347,17 +347,18 @@ std::string format_mac_address_pretty(const uint8_t *mac) {
|
|||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase
|
// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase.
|
||||||
|
// When separator is set, it is written unconditionally after each byte and the last
|
||||||
|
// one is overwritten with '\0', eliminating the per-byte `i < length - 1` check.
|
||||||
static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator,
|
static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator,
|
||||||
char base) {
|
char base) {
|
||||||
if (length == 0) {
|
if (length == 0 || buffer_size == 0) {
|
||||||
|
if (buffer_size > 0)
|
||||||
buffer[0] = '\0';
|
buffer[0] = '\0';
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
// With separator: total length is 3*length (2*length hex chars, (length-1) separators, 1 null terminator)
|
|
||||||
// Without separator: total length is 2*length + 1 (2*length hex chars, 1 null terminator)
|
|
||||||
uint8_t stride = separator ? 3 : 2;
|
uint8_t stride = separator ? 3 : 2;
|
||||||
size_t max_bytes = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride);
|
size_t max_bytes = separator ? (buffer_size / 3) : ((buffer_size - 1) / 2);
|
||||||
if (max_bytes == 0) {
|
if (max_bytes == 0) {
|
||||||
buffer[0] = '\0';
|
buffer[0] = '\0';
|
||||||
return buffer;
|
return buffer;
|
||||||
@@ -369,14 +370,30 @@ static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t
|
|||||||
size_t pos = i * stride;
|
size_t pos = i * stride;
|
||||||
buffer[pos] = format_hex_char(data[i] >> 4, base);
|
buffer[pos] = format_hex_char(data[i] >> 4, base);
|
||||||
buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base);
|
buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base);
|
||||||
if (separator && i < length - 1) {
|
if (separator) {
|
||||||
buffer[pos + 2] = separator;
|
buffer[pos + 2] = separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// With separator: overwrite last separator with '\0'
|
||||||
|
// Without: write '\0' after last hex char
|
||||||
buffer[length * stride - (separator ? 1 : 0)] = '\0';
|
buffer[length * stride - (separator ? 1 : 0)] = '\0';
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *uint32_to_str_unchecked(char *buf, uint32_t val) {
|
||||||
|
if (val == 0) {
|
||||||
|
*buf++ = '0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
char *start = buf;
|
||||||
|
while (val > 0) {
|
||||||
|
*buf++ = '0' + (val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
std::reverse(start, buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
|
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
|
||||||
return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
|
return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-3
@@ -1263,13 +1263,13 @@ constexpr uint8_t parse_hex_char(char c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase)
|
/// Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase)
|
||||||
inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; }
|
ESPHOME_ALWAYS_INLINE inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; }
|
||||||
|
|
||||||
/// Convert a nibble (0-15) to lowercase hex char
|
/// Convert a nibble (0-15) to lowercase hex char
|
||||||
inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); }
|
ESPHOME_ALWAYS_INLINE inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); }
|
||||||
|
|
||||||
/// Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
|
/// Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
|
||||||
inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); }
|
ESPHOME_ALWAYS_INLINE inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); }
|
||||||
|
|
||||||
/// Write int8 value to buffer without modulo operations.
|
/// Write int8 value to buffer without modulo operations.
|
||||||
/// Buffer must have at least 4 bytes free. Returns pointer past last char written.
|
/// Buffer must have at least 4 bytes free. Returns pointer past last char written.
|
||||||
@@ -1295,6 +1295,21 @@ inline char *int8_to_str(char *buf, int8_t val) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Minimum buffer size for uint32_to_str: 10 digits + null terminator.
|
||||||
|
static constexpr size_t UINT32_MAX_STR_SIZE = 11;
|
||||||
|
|
||||||
|
/// Write unsigned 32-bit integer to buffer (internal, no size check).
|
||||||
|
/// Buffer must have at least 10 bytes free. Returns pointer past last char written.
|
||||||
|
char *uint32_to_str_unchecked(char *buf, uint32_t val);
|
||||||
|
|
||||||
|
/// Write unsigned 32-bit integer to buffer with compile-time size check.
|
||||||
|
/// Null-terminates the output. Returns number of chars written (excluding null).
|
||||||
|
inline size_t uint32_to_str(std::span<char, UINT32_MAX_STR_SIZE> buf, uint32_t val) {
|
||||||
|
char *end = uint32_to_str_unchecked(buf.data(), val);
|
||||||
|
*end = '\0';
|
||||||
|
return static_cast<size_t>(end - buf.data());
|
||||||
|
}
|
||||||
|
|
||||||
/// Format byte array as lowercase hex to buffer (base implementation).
|
/// Format byte array as lowercase hex to buffer (base implementation).
|
||||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
|
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class Scheduler {
|
|||||||
// (single-threaded). This is safe because the main loop is the only thread
|
// (single-threaded). This is safe because the main loop is the only thread
|
||||||
// that reads to_add_ without holding lock_; other threads may read it only
|
// that reads to_add_ without holding lock_; other threads may read it only
|
||||||
// while holding the mutex (e.g. cancel_item_locked_).
|
// while holding the mutex (e.g. cancel_item_locked_).
|
||||||
inline void HOT process_to_add() {
|
inline void ESPHOME_ALWAYS_INLINE HOT process_to_add() {
|
||||||
if (this->to_add_empty_())
|
if (this->to_add_empty_())
|
||||||
return;
|
return;
|
||||||
this->process_to_add_slow_path_();
|
this->process_to_add_slow_path_();
|
||||||
@@ -302,7 +302,7 @@ class Scheduler {
|
|||||||
// loop thread structurally modifies items_ (push/pop/erase). Other threads may
|
// loop thread structurally modifies items_ (push/pop/erase). Other threads may
|
||||||
// iterate items_ and mark items removed under lock_, but never change the
|
// iterate items_ and mark items removed under lock_, but never change the
|
||||||
// vector's size or data pointer.
|
// vector's size or data pointer.
|
||||||
inline bool HOT cleanup_() {
|
inline bool ESPHOME_ALWAYS_INLINE HOT cleanup_() {
|
||||||
if (this->to_remove_empty_())
|
if (this->to_remove_empty_())
|
||||||
return !this->items_.empty();
|
return !this->items_.empty();
|
||||||
return this->cleanup_slow_path_();
|
return this->cleanup_slow_path_();
|
||||||
@@ -407,7 +407,7 @@ class Scheduler {
|
|||||||
// Process defer queue for FIFO execution of deferred items.
|
// Process defer queue for FIFO execution of deferred items.
|
||||||
// IMPORTANT: This method should only be called from the main thread (loop task).
|
// IMPORTANT: This method should only be called from the main thread (loop task).
|
||||||
// Inlined: the fast path (nothing deferred) is just an atomic load check.
|
// Inlined: the fast path (nothing deferred) is just an atomic load check.
|
||||||
inline void HOT process_defer_queue_(uint32_t &now) {
|
inline void ESPHOME_ALWAYS_INLINE HOT process_defer_queue_(uint32_t &now) {
|
||||||
// Fast path: nothing to process, avoid lock entirely.
|
// Fast path: nothing to process, avoid lock entirely.
|
||||||
// Worst case is a one-loop-iteration delay before newly deferred items are processed.
|
// Worst case is a one-loop-iteration delay before newly deferred items are processed.
|
||||||
if (this->defer_empty_())
|
if (this->defer_empty_())
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ def _generate_source_table_code(
|
|||||||
entries = ", ".join(var_names)
|
entries = ", ".join(var_names)
|
||||||
lines.append(f"static const char *const {table_var}[] PROGMEM = {{{entries}}};")
|
lines.append(f"static const char *const {table_var}[] PROGMEM = {{{entries}}};")
|
||||||
lines.append(f"const LogString *{lookup_fn}(uint8_t index) {{")
|
lines.append(f"const LogString *{lookup_fn}(uint8_t index) {{")
|
||||||
lines.append(f' if (index == 0 || index > {count}) return LOG_STR("<unknown>");')
|
cond = "index == 0" if count >= 255 else f"index == 0 || index > {count}"
|
||||||
|
lines.append(f' if ({cond}) return LOG_STR("<unknown>");')
|
||||||
lines.append(" return reinterpret_cast<const LogString *>(")
|
lines.append(" return reinterpret_cast<const LogString *>(")
|
||||||
lines.append(f" progmem_read_ptr(&{table_var}[index - 1]));")
|
lines.append(f" progmem_read_ptr(&{table_var}[index - 1]));")
|
||||||
lines.append("}")
|
lines.append("}")
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ dependencies:
|
|||||||
version: "7.4.2"
|
version: "7.4.2"
|
||||||
esphome/esp-audio-libs:
|
esphome/esp-audio-libs:
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
|
esphome/micro-decoder:
|
||||||
|
version: 0.1.1
|
||||||
esphome/micro-flac:
|
esphome/micro-flac:
|
||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
esphome/micro-opus:
|
esphome/micro-opus:
|
||||||
@@ -14,7 +16,7 @@ dependencies:
|
|||||||
espressif/esp32-camera:
|
espressif/esp32-camera:
|
||||||
version: 2.1.6
|
version: 2.1.6
|
||||||
espressif/mdns:
|
espressif/mdns:
|
||||||
version: 1.10.0
|
version: 1.11.0
|
||||||
espressif/esp_wifi_remote:
|
espressif/esp_wifi_remote:
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
rules:
|
rules:
|
||||||
|
|||||||
+3
-166
@@ -5,157 +5,15 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import sys
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
from esphome.util import run_external_command, run_external_process
|
from esphome.util import run_external_process
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def patch_structhash():
|
|
||||||
# Patch platformio's structhash to not recompile the entire project when files are
|
|
||||||
# removed/added. This might have unintended consequences, but this improves compile
|
|
||||||
# times greatly when adding/removing components and a simple clean build solves
|
|
||||||
# all issues
|
|
||||||
from platformio.run import cli, helpers
|
|
||||||
|
|
||||||
def patched_clean_build_dir(build_dir, *args):
|
|
||||||
from platformio import fs
|
|
||||||
from platformio.project.helpers import get_project_dir
|
|
||||||
|
|
||||||
platformio_ini = Path(get_project_dir()) / "platformio.ini"
|
|
||||||
|
|
||||||
build_dir = Path(build_dir)
|
|
||||||
|
|
||||||
# if project's config is modified
|
|
||||||
if (
|
|
||||||
build_dir.is_dir()
|
|
||||||
and platformio_ini.stat().st_mtime > build_dir.stat().st_mtime
|
|
||||||
):
|
|
||||||
fs.rmtree(build_dir)
|
|
||||||
|
|
||||||
if not build_dir.is_dir():
|
|
||||||
build_dir.mkdir(parents=True)
|
|
||||||
|
|
||||||
helpers.clean_build_dir = patched_clean_build_dir
|
|
||||||
cli.clean_build_dir = patched_clean_build_dir
|
|
||||||
|
|
||||||
|
|
||||||
def patch_file_downloader():
|
|
||||||
"""Patch PlatformIO's FileDownloader to retry on PackageException errors.
|
|
||||||
|
|
||||||
PlatformIO's FileDownloader uses HTTPSession which lacks built-in retry
|
|
||||||
for 502/503 errors. We add retries with exponential backoff and close the
|
|
||||||
session between attempts to force a fresh TCP connection, which may route
|
|
||||||
to a different CDN edge node.
|
|
||||||
"""
|
|
||||||
from platformio.package.download import FileDownloader
|
|
||||||
from platformio.package.exception import PackageException
|
|
||||||
|
|
||||||
if getattr(FileDownloader.__init__, "_esphome_patched", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
original_init = FileDownloader.__init__
|
|
||||||
|
|
||||||
def patched_init(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
max_retries = 5
|
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
|
||||||
try:
|
|
||||||
original_init(self, *args, **kwargs)
|
|
||||||
return
|
|
||||||
except PackageException as e:
|
|
||||||
if attempt < max_retries - 1:
|
|
||||||
# Exponential backoff: 2, 4, 8, 16 seconds
|
|
||||||
delay = 2 ** (attempt + 1)
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Package download failed: %s. "
|
|
||||||
"Retrying in %d seconds... (attempt %d/%d)",
|
|
||||||
str(e),
|
|
||||||
delay,
|
|
||||||
attempt + 1,
|
|
||||||
max_retries,
|
|
||||||
)
|
|
||||||
# Close the response and session to free resources
|
|
||||||
# and force a new TCP connection on retry, which may
|
|
||||||
# route to a different CDN edge node
|
|
||||||
# pylint: disable=protected-access,broad-except
|
|
||||||
try:
|
|
||||||
if (
|
|
||||||
hasattr(self, "_http_response")
|
|
||||||
and self._http_response is not None
|
|
||||||
):
|
|
||||||
self._http_response.close()
|
|
||||||
if hasattr(self, "_http_session"):
|
|
||||||
self._http_session.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# pylint: enable=protected-access,broad-except
|
|
||||||
time.sleep(delay)
|
|
||||||
else:
|
|
||||||
# Final attempt - re-raise
|
|
||||||
raise
|
|
||||||
|
|
||||||
patched_init._esphome_patched = True # type: ignore[attr-defined] # pylint: disable=protected-access
|
|
||||||
FileDownloader.__init__ = patched_init
|
|
||||||
|
|
||||||
|
|
||||||
IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})"
|
|
||||||
FILTER_PLATFORMIO_LINES = [
|
|
||||||
r"Verbose mode can be enabled via `-v, --verbose` option.*",
|
|
||||||
r"CONFIGURATION: https://docs.platformio.org/.*",
|
|
||||||
r"DEBUG: Current.*",
|
|
||||||
r"LDF Modes:.*",
|
|
||||||
r"LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf.*",
|
|
||||||
f"Looking for {IGNORE_LIB_WARNINGS} library in registry",
|
|
||||||
f"Warning! Library `.*'{IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.",
|
|
||||||
f"You can ignore this message, if `.*{IGNORE_LIB_WARNINGS}.*` is a built-in library.*",
|
|
||||||
r"Scanning dependencies...",
|
|
||||||
r"Found \d+ compatible libraries",
|
|
||||||
r"Memory Usage -> https://bit.ly/pio-memory-usage",
|
|
||||||
r"Found: https://platformio.org/lib/show/.*",
|
|
||||||
r"Using cache: .*",
|
|
||||||
r"Installing dependencies",
|
|
||||||
r"Library Manager: Already installed, built-in library",
|
|
||||||
r"Building in .* mode",
|
|
||||||
r"Advanced Memory Usage is available via .*",
|
|
||||||
r"Merged .* ELF section",
|
|
||||||
r"esptool.py v.*",
|
|
||||||
r"esptool v.*",
|
|
||||||
r"Checking size .*",
|
|
||||||
r"Retrieving maximum program size .*",
|
|
||||||
r"PLATFORM: .*",
|
|
||||||
r"PACKAGES:.*",
|
|
||||||
r" - framework-arduinoespressif.* \(.*\)",
|
|
||||||
r" - tool-esptool.* \(.*\)",
|
|
||||||
r" - toolchain-.* \(.*\)",
|
|
||||||
r"Creating BIN file .*",
|
|
||||||
r"Warning! Could not find file \".*.crt\"",
|
|
||||||
r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
|
|
||||||
r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.",
|
|
||||||
r"Warning: esp-idf-size exited with code 2",
|
|
||||||
r"esp_idf_size: error: unrecognized arguments: --ng",
|
|
||||||
r"Package configuration completed successfully",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformioLogFilter(logging.Filter):
|
|
||||||
"""Filter to suppress noisy platformio log messages."""
|
|
||||||
|
|
||||||
_PATTERN = re.compile(
|
|
||||||
r"|".join(r"(?:" + pattern + r")" for pattern in FILTER_PLATFORMIO_LINES)
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter(self, record: logging.LogRecord) -> bool:
|
|
||||||
# Only filter messages from platformio-related loggers
|
|
||||||
if "platformio" not in record.name.lower():
|
|
||||||
return True
|
|
||||||
return self._PATTERN.match(record.getMessage()) is None
|
|
||||||
|
|
||||||
|
|
||||||
def run_platformio_cli(*args, **kwargs) -> str | int:
|
def run_platformio_cli(*args, **kwargs) -> str | int:
|
||||||
os.environ["PLATFORMIO_FORCE_COLOR"] = "true"
|
os.environ["PLATFORMIO_FORCE_COLOR"] = "true"
|
||||||
os.environ["PLATFORMIO_BUILD_DIR"] = str(CORE.relative_pioenvs_path().absolute())
|
os.environ["PLATFORMIO_BUILD_DIR"] = str(CORE.relative_pioenvs_path().absolute())
|
||||||
@@ -166,31 +24,10 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
|
|||||||
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
||||||
# Increase uv retry count to handle transient network errors (default is 3)
|
# Increase uv retry count to handle transient network errors (default is 3)
|
||||||
os.environ.setdefault("UV_HTTP_RETRIES", "10")
|
os.environ.setdefault("UV_HTTP_RETRIES", "10")
|
||||||
cmd = ["platformio"] + list(args)
|
cmd = [sys.executable, "-m", "esphome.platformio_runner"] + list(args)
|
||||||
|
|
||||||
if not CORE.verbose:
|
|
||||||
kwargs["filter_lines"] = FILTER_PLATFORMIO_LINES
|
|
||||||
|
|
||||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is not None:
|
|
||||||
return run_external_process(*cmd, **kwargs)
|
return run_external_process(*cmd, **kwargs)
|
||||||
|
|
||||||
import platformio.__main__
|
|
||||||
|
|
||||||
patch_structhash()
|
|
||||||
patch_file_downloader()
|
|
||||||
|
|
||||||
# Add log filter to suppress noisy platformio messages
|
|
||||||
log_filter = PlatformioLogFilter() if not CORE.verbose else None
|
|
||||||
if log_filter:
|
|
||||||
for handler in logging.getLogger().handlers:
|
|
||||||
handler.addFilter(log_filter)
|
|
||||||
try:
|
|
||||||
return run_external_command(platformio.__main__.main, *cmd, **kwargs)
|
|
||||||
finally:
|
|
||||||
if log_filter:
|
|
||||||
for handler in logging.getLogger().handlers:
|
|
||||||
handler.removeFilter(log_filter)
|
|
||||||
|
|
||||||
|
|
||||||
def run_platformio_cli_run(config, verbose, *args, **kwargs) -> str | int:
|
def run_platformio_cli_run(config, verbose, *args, **kwargs) -> str | int:
|
||||||
command = ["run", "-d", str(CORE.build_path)]
|
command = ["run", "-d", str(CORE.build_path)]
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
"""Subprocess entry point that applies ESPHome's PlatformIO patches.
|
||||||
|
|
||||||
|
Invoked via ``python -m esphome.platformio_runner`` instead of
|
||||||
|
``python -m platformio`` so that the patches (incremental rebuild
|
||||||
|
preservation, download retries) apply inside the subprocess. Running
|
||||||
|
PlatformIO in a subprocess keeps its ``sys.path`` mutations and other
|
||||||
|
global state from leaking into the ESPHome process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def patch_structhash() -> None:
|
||||||
|
"""Avoid full rebuilds when files are added or removed.
|
||||||
|
|
||||||
|
PlatformIO clears the build dir whenever its structure hash changes.
|
||||||
|
We replace that with an mtime check against ``platformio.ini`` so
|
||||||
|
incremental builds are preserved unless the project config changed.
|
||||||
|
"""
|
||||||
|
from platformio.run import cli, helpers
|
||||||
|
|
||||||
|
def patched_clean_build_dir(build_dir, *_args):
|
||||||
|
from platformio import fs
|
||||||
|
from platformio.project.helpers import get_project_dir
|
||||||
|
|
||||||
|
platformio_ini = Path(get_project_dir()) / "platformio.ini"
|
||||||
|
build_dir = Path(build_dir)
|
||||||
|
|
||||||
|
if (
|
||||||
|
build_dir.is_dir()
|
||||||
|
and platformio_ini.stat().st_mtime > build_dir.stat().st_mtime
|
||||||
|
):
|
||||||
|
fs.rmtree(build_dir)
|
||||||
|
|
||||||
|
if not build_dir.is_dir():
|
||||||
|
build_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
helpers.clean_build_dir = patched_clean_build_dir
|
||||||
|
cli.clean_build_dir = patched_clean_build_dir
|
||||||
|
|
||||||
|
|
||||||
|
def patch_file_downloader() -> None:
|
||||||
|
"""Retry PlatformIO package downloads with exponential backoff.
|
||||||
|
|
||||||
|
PlatformIO's ``FileDownloader`` uses an ``HTTPSession`` without built-in
|
||||||
|
retry for 502/503 errors. We wrap ``__init__`` to retry on
|
||||||
|
``PackageException`` and close the session between attempts so a new
|
||||||
|
TCP connection can route to a different CDN edge node.
|
||||||
|
"""
|
||||||
|
from platformio.package.download import FileDownloader
|
||||||
|
from platformio.package.exception import PackageException
|
||||||
|
|
||||||
|
if getattr(FileDownloader.__init__, "_esphome_patched", False):
|
||||||
|
return
|
||||||
|
|
||||||
|
original_init = FileDownloader.__init__
|
||||||
|
|
||||||
|
def patched_init(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
max_retries = 5
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
original_init(self, *args, **kwargs)
|
||||||
|
return
|
||||||
|
except PackageException as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
delay = 2 ** (attempt + 1)
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Package download failed: %s. "
|
||||||
|
"Retrying in %d seconds... (attempt %d/%d)",
|
||||||
|
str(e),
|
||||||
|
delay,
|
||||||
|
attempt + 1,
|
||||||
|
max_retries,
|
||||||
|
)
|
||||||
|
# pylint: disable=protected-access,broad-except
|
||||||
|
try:
|
||||||
|
if (
|
||||||
|
hasattr(self, "_http_response")
|
||||||
|
and self._http_response is not None
|
||||||
|
):
|
||||||
|
self._http_response.close()
|
||||||
|
if hasattr(self, "_http_session"):
|
||||||
|
self._http_session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# pylint: enable=protected-access,broad-except
|
||||||
|
time.sleep(delay)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
patched_init._esphome_patched = True # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||||
|
FileDownloader.__init__ = patched_init
|
||||||
|
|
||||||
|
|
||||||
|
_IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})"
|
||||||
|
# Regex patterns matched against each line of PlatformIO output. Lines that
|
||||||
|
# match are dropped by RedirectText before they reach the parent process.
|
||||||
|
# Patterns are anchored at the start of the line (RedirectText uses
|
||||||
|
# ``re.match``). Disabled when the user passes ``-v`` / ``--verbose`` to
|
||||||
|
# ``esphome compile``.
|
||||||
|
FILTER_PLATFORMIO_LINES = [
|
||||||
|
r"Verbose mode can be enabled via `-v, --verbose` option.*",
|
||||||
|
r"CONFIGURATION: https://docs.platformio.org/.*",
|
||||||
|
r"DEBUG: Current.*",
|
||||||
|
r"LDF Modes:.*",
|
||||||
|
r"LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf.*",
|
||||||
|
f"Looking for {_IGNORE_LIB_WARNINGS} library in registry",
|
||||||
|
f"Warning! Library `.*'{_IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.",
|
||||||
|
f"You can ignore this message, if `.*{_IGNORE_LIB_WARNINGS}.*` is a built-in library.*",
|
||||||
|
r"Scanning dependencies...",
|
||||||
|
r"Found \d+ compatible libraries",
|
||||||
|
r"Memory Usage -> https://bit.ly/pio-memory-usage",
|
||||||
|
r"Found: https://platformio.org/lib/show/.*",
|
||||||
|
r"Using cache: .*",
|
||||||
|
r"Installing dependencies",
|
||||||
|
r"Library Manager: Already installed, built-in library",
|
||||||
|
r"Building in .* mode",
|
||||||
|
r"Advanced Memory Usage is available via .*",
|
||||||
|
r"Merged .* ELF section",
|
||||||
|
r"esptool.py v.*",
|
||||||
|
r"esptool v.*",
|
||||||
|
r"Checking size .*",
|
||||||
|
r"Retrieving maximum program size .*",
|
||||||
|
r"PLATFORM: .*",
|
||||||
|
r"PACKAGES:.*",
|
||||||
|
r" - framework-arduinoespressif.* \(.*\)",
|
||||||
|
r" - tool-esptool.* \(.*\)",
|
||||||
|
r" - toolchain-.* \(.*\)",
|
||||||
|
r"Creating BIN file .*",
|
||||||
|
r"Warning! Could not find file \".*.crt\"",
|
||||||
|
r"Warning! Arduino framework as an ESP-IDF component doesn't handle the `variant` field! The default `esp32` variant will be used.",
|
||||||
|
r"Warning: DEPRECATED: 'esptool.py' is deprecated. Please use 'esptool' instead. The '.py' suffix will be removed in a future major release.",
|
||||||
|
r"Warning: esp-idf-size exited with code 2",
|
||||||
|
r"esp_idf_size: error: unrecognized arguments: --ng",
|
||||||
|
r"Package configuration completed successfully",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
patch_structhash()
|
||||||
|
patch_file_downloader()
|
||||||
|
|
||||||
|
# Wrap stdout/stderr with RedirectText before PlatformIO runs:
|
||||||
|
#
|
||||||
|
# 1. RedirectText.isatty() unconditionally returns True. Click, tqdm, and
|
||||||
|
# PlatformIO's own progress-bar code check ``stream.isatty()`` to
|
||||||
|
# decide whether to emit TTY-format output (``\r`` cursor moves, ANSI
|
||||||
|
# colors, fancy progress bars). With the wrapper in place they always
|
||||||
|
# emit TTY format, even when our real stdout is a pipe to the parent
|
||||||
|
# process. Downstream consumers (local terminals and the Home
|
||||||
|
# Assistant dashboard log viewer) render the TTY control sequences
|
||||||
|
# correctly, so the user sees real progress bars.
|
||||||
|
#
|
||||||
|
# 2. FILTER_PLATFORMIO_LINES is applied inside RedirectText.write() in
|
||||||
|
# this subprocess, so noisy PlatformIO output is dropped before it
|
||||||
|
# ever leaves the runner. This replaces the parent-side filtering
|
||||||
|
# that was lost when we switched from in-process to subprocess — the
|
||||||
|
# parent's ``subprocess.run`` uses ``.fileno()`` on RedirectText and
|
||||||
|
# bypasses its ``write()`` path entirely.
|
||||||
|
#
|
||||||
|
# Filtering is disabled when the user passed -v / --verbose to
|
||||||
|
# ``esphome compile``, preserving the previous in-process behavior where
|
||||||
|
# verbose mode let all PlatformIO output through unfiltered.
|
||||||
|
from esphome.util import RedirectText
|
||||||
|
|
||||||
|
is_verbose = any(arg in ("-v", "--verbose") for arg in sys.argv[1:])
|
||||||
|
filter_lines = None if is_verbose else FILTER_PLATFORMIO_LINES
|
||||||
|
|
||||||
|
sys.stdout = RedirectText(sys.stdout, filter_lines=filter_lines)
|
||||||
|
sys.stderr = RedirectText(sys.stderr, filter_lines=filter_lines)
|
||||||
|
|
||||||
|
import platformio.__main__
|
||||||
|
|
||||||
|
return platformio.__main__.main() or 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user