mirror of
https://github.com/esphome/esphome.git
synced 2025-08-02 08:27:47 +00:00
Merge branch 'dev' into clang-tidy_broken_dep_chain
This commit is contained in:
commit
ae5e69a04a
@ -1,6 +1,13 @@
|
|||||||
---
|
---
|
||||||
# See https://pre-commit.com for more information
|
# See https://pre-commit.com for more information
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
|
||||||
|
ci:
|
||||||
|
autoupdate_commit_msg: 'pre-commit: autoupdate'
|
||||||
|
autoupdate_schedule: weekly
|
||||||
|
# Skip hooks that have issues in pre-commit CI environment
|
||||||
|
skip: [pylint, clang-tidy-hash, yamllint]
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_PIN
|
from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
from .. import gpio_ns
|
from .. import gpio_ns
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
GPIOBinarySensor = gpio_ns.class_(
|
GPIOBinarySensor = gpio_ns.class_(
|
||||||
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||||
)
|
)
|
||||||
@ -41,6 +46,22 @@ async def to_code(config):
|
|||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
cg.add(var.set_pin(pin))
|
cg.add(var.set_pin(pin))
|
||||||
|
|
||||||
cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
|
# Check for ESP8266 GPIO16 interrupt limitation
|
||||||
if config[CONF_USE_INTERRUPT]:
|
# GPIO16 on ESP8266 is a special pin that doesn't support interrupts through
|
||||||
|
# the Arduino attachInterrupt() function. This is the only known GPIO pin
|
||||||
|
# across all supported platforms that has this limitation, so we handle it
|
||||||
|
# here instead of in the platform-specific code.
|
||||||
|
use_interrupt = config[CONF_USE_INTERRUPT]
|
||||||
|
if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. "
|
||||||
|
"Falling back to polling mode (same as in ESPHome <2025.7). "
|
||||||
|
"The sensor will work exactly as before, but other pins have better "
|
||||||
|
"performance with interrupts.",
|
||||||
|
config.get(CONF_NAME, config[CONF_ID]),
|
||||||
|
)
|
||||||
|
use_interrupt = False
|
||||||
|
|
||||||
|
cg.add(var.set_use_interrupt(use_interrupt))
|
||||||
|
if use_interrupt:
|
||||||
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
||||||
|
@ -180,7 +180,7 @@ async def to_code(config):
|
|||||||
await speaker.register_speaker(var, config)
|
await speaker.register_speaker(var, config)
|
||||||
|
|
||||||
if config[CONF_DAC_TYPE] == "internal":
|
if config[CONF_DAC_TYPE] == "internal":
|
||||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||||
if use_legacy():
|
if use_legacy():
|
||||||
|
@ -29,9 +29,9 @@ from ..defines import (
|
|||||||
)
|
)
|
||||||
from ..helpers import add_lv_use, lvgl_components_required
|
from ..helpers import add_lv_use, lvgl_components_required
|
||||||
from ..lv_validation import (
|
from ..lv_validation import (
|
||||||
angle,
|
|
||||||
get_end_value,
|
get_end_value,
|
||||||
get_start_value,
|
get_start_value,
|
||||||
|
lv_angle,
|
||||||
lv_bool,
|
lv_bool,
|
||||||
lv_color,
|
lv_color,
|
||||||
lv_float,
|
lv_float,
|
||||||
@ -162,7 +162,7 @@ SCALE_SCHEMA = cv.Schema(
|
|||||||
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
|
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
|
||||||
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
|
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
|
||||||
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
|
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
|
||||||
cv.Optional(CONF_ROTATION): angle,
|
cv.Optional(CONF_ROTATION): lv_angle,
|
||||||
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
|
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -187,7 +187,7 @@ class MeterType(WidgetType):
|
|||||||
for scale_conf in config.get(CONF_SCALES, ()):
|
for scale_conf in config.get(CONF_SCALES, ()):
|
||||||
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
|
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
|
||||||
if CONF_ROTATION in scale_conf:
|
if CONF_ROTATION in scale_conf:
|
||||||
rotation = scale_conf[CONF_ROTATION] // 10
|
rotation = await lv_angle.process(scale_conf[CONF_ROTATION])
|
||||||
with LocalVariable(
|
with LocalVariable(
|
||||||
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
|
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
|
||||||
) as meter_var:
|
) as meter_var:
|
||||||
@ -205,21 +205,20 @@ class MeterType(WidgetType):
|
|||||||
var,
|
var,
|
||||||
meter_var,
|
meter_var,
|
||||||
ticks[CONF_COUNT],
|
ticks[CONF_COUNT],
|
||||||
ticks[CONF_WIDTH],
|
await size.process(ticks[CONF_WIDTH]),
|
||||||
ticks[CONF_LENGTH],
|
await size.process(ticks[CONF_LENGTH]),
|
||||||
color,
|
color,
|
||||||
)
|
)
|
||||||
if CONF_MAJOR in ticks:
|
if CONF_MAJOR in ticks:
|
||||||
major = ticks[CONF_MAJOR]
|
major = ticks[CONF_MAJOR]
|
||||||
color = await lv_color.process(major[CONF_COLOR])
|
|
||||||
lv.meter_set_scale_major_ticks(
|
lv.meter_set_scale_major_ticks(
|
||||||
var,
|
var,
|
||||||
meter_var,
|
meter_var,
|
||||||
major[CONF_STRIDE],
|
major[CONF_STRIDE],
|
||||||
major[CONF_WIDTH],
|
await size.process(major[CONF_WIDTH]),
|
||||||
major[CONF_LENGTH],
|
await size.process(major[CONF_LENGTH]),
|
||||||
color,
|
await lv_color.process(major[CONF_COLOR]),
|
||||||
major[CONF_LABEL_GAP],
|
await size.process(major[CONF_LABEL_GAP]),
|
||||||
)
|
)
|
||||||
for indicator in scale_conf.get(CONF_INDICATORS, ()):
|
for indicator in scale_conf.get(CONF_INDICATORS, ()):
|
||||||
(t, v) = next(iter(indicator.items()))
|
(t, v) = next(iter(indicator.items()))
|
||||||
@ -233,7 +232,11 @@ class MeterType(WidgetType):
|
|||||||
lv_assign(
|
lv_assign(
|
||||||
ivar,
|
ivar,
|
||||||
lv_expr.meter_add_needle_line(
|
lv_expr.meter_add_needle_line(
|
||||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
var,
|
||||||
|
meter_var,
|
||||||
|
await size.process(v[CONF_WIDTH]),
|
||||||
|
color,
|
||||||
|
await size.process(v[CONF_R_MOD]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if t == CONF_ARC:
|
if t == CONF_ARC:
|
||||||
@ -241,7 +244,11 @@ class MeterType(WidgetType):
|
|||||||
lv_assign(
|
lv_assign(
|
||||||
ivar,
|
ivar,
|
||||||
lv_expr.meter_add_arc(
|
lv_expr.meter_add_arc(
|
||||||
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD]
|
var,
|
||||||
|
meter_var,
|
||||||
|
await size.process(v[CONF_WIDTH]),
|
||||||
|
color,
|
||||||
|
await size.process(v[CONF_R_MOD]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if t == CONF_TICK_STYLE:
|
if t == CONF_TICK_STYLE:
|
||||||
@ -257,7 +264,7 @@ class MeterType(WidgetType):
|
|||||||
color_start,
|
color_start,
|
||||||
color_end,
|
color_end,
|
||||||
v[CONF_LOCAL],
|
v[CONF_LOCAL],
|
||||||
v[CONF_WIDTH],
|
size.process(v[CONF_WIDTH]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if t == CONF_IMAGE:
|
if t == CONF_IMAGE:
|
||||||
|
@ -146,8 +146,11 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
|||||||
if sub is not None:
|
if sub is not None:
|
||||||
item[k] = sub
|
item[k] = sub
|
||||||
for old, new in replace_keys:
|
for old, new in replace_keys:
|
||||||
item[new] = merge_config(item.get(old), item.get(new))
|
if str(new) == str(old):
|
||||||
del item[old]
|
item[new] = item[old]
|
||||||
|
else:
|
||||||
|
item[new] = merge_config(item.get(old), item.get(new))
|
||||||
|
del item[old]
|
||||||
elif isinstance(item, str):
|
elif isinstance(item, str):
|
||||||
sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing)
|
sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing)
|
||||||
if isinstance(sub, JinjaStr) or sub != item:
|
if isinstance(sub, JinjaStr) or sub != item:
|
||||||
|
@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
|||||||
esptool==4.9.0
|
esptool==4.9.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250514.0
|
esphome-dashboard==20250514.0
|
||||||
aioesphomeapi==34.2.1
|
aioesphomeapi==35.0.1
|
||||||
zeroconf==0.147.0
|
zeroconf==0.147.0
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.18.14 # dashboard_import
|
ruamel.yaml==0.18.14 # dashboard_import
|
||||||
|
69
tests/component_tests/gpio/test_gpio_binary_sensor.py
Normal file
69
tests/component_tests/gpio/test_gpio_binary_sensor.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Tests for the GPIO binary sensor component."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpio_binary_sensor_basic_setup(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
When the GPIO binary sensor is set in the yaml file, it should be registered in main
|
||||||
|
"""
|
||||||
|
main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml")
|
||||||
|
|
||||||
|
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||||
|
assert "App.register_binary_sensor" in main_cpp
|
||||||
|
assert "bs_gpio->set_use_interrupt(true);" in main_cpp
|
||||||
|
assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpio_binary_sensor_esp8266_gpio16_disables_interrupt(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test that ESP8266 GPIO16 automatically disables interrupt mode with a warning
|
||||||
|
"""
|
||||||
|
main_cpp = generate_main(
|
||||||
|
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that interrupt is disabled for GPIO16
|
||||||
|
assert "bs_gpio16->set_use_interrupt(false);" in main_cpp
|
||||||
|
|
||||||
|
# Check that the warning was logged
|
||||||
|
assert "GPIO16 on ESP8266 doesn't support interrupts" in caplog.text
|
||||||
|
assert "Falling back to polling mode" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test that ESP8266 pins other than GPIO16 still use interrupt mode
|
||||||
|
"""
|
||||||
|
main_cpp = generate_main(
|
||||||
|
"tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
# GPIO5 should still use interrupts
|
||||||
|
assert "bs_gpio5->set_use_interrupt(true);" in main_cpp
|
||||||
|
assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpio_binary_sensor_explicit_polling_mode(
|
||||||
|
generate_main: Callable[[str | Path], str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test that explicitly setting use_interrupt: false works
|
||||||
|
"""
|
||||||
|
main_cpp = generate_main(
|
||||||
|
"tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "bs_polling->set_use_interrupt(false);" in main_cpp
|
11
tests/component_tests/gpio/test_gpio_binary_sensor.yaml
Normal file
11
tests/component_tests/gpio/test_gpio_binary_sensor.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
pin: 5
|
||||||
|
name: "Test GPIO Binary Sensor"
|
||||||
|
id: bs_gpio
|
@ -0,0 +1,20 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: 16
|
||||||
|
mode: INPUT_PULLDOWN_16
|
||||||
|
name: "GPIO16 Touch Sensor"
|
||||||
|
id: bs_gpio16
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: 5
|
||||||
|
mode: INPUT_PULLUP
|
||||||
|
name: "GPIO5 Button"
|
||||||
|
id: bs_gpio5
|
@ -0,0 +1,12 @@
|
|||||||
|
esphome:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32dev
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
pin: 5
|
||||||
|
name: "Polling Mode Sensor"
|
||||||
|
id: bs_polling
|
||||||
|
use_interrupt: false
|
@ -919,21 +919,21 @@ lvgl:
|
|||||||
text_color: 0xFFFFFF
|
text_color: 0xFFFFFF
|
||||||
scales:
|
scales:
|
||||||
- ticks:
|
- ticks:
|
||||||
width: 1
|
width: !lambda return 1;
|
||||||
count: 61
|
count: 61
|
||||||
length: 20
|
length: 20%
|
||||||
color: 0xFFFFFF
|
color: 0xFFFFFF
|
||||||
range_from: 0
|
range_from: 0
|
||||||
range_to: 60
|
range_to: 60
|
||||||
angle_range: 360
|
angle_range: 360
|
||||||
rotation: 270
|
rotation: !lambda return 2700;
|
||||||
indicators:
|
indicators:
|
||||||
- line:
|
- line:
|
||||||
opa: 50%
|
opa: 50%
|
||||||
id: minute_hand
|
id: minute_hand
|
||||||
color: 0xFF0000
|
color: 0xFF0000
|
||||||
r_mod: -1
|
r_mod: !lambda return -1;
|
||||||
width: 3
|
width: !lambda return 3;
|
||||||
-
|
-
|
||||||
angle_range: 330
|
angle_range: 330
|
||||||
rotation: 300
|
rotation: 300
|
||||||
|
@ -17,3 +17,5 @@ test_list:
|
|||||||
- ${undefined_var}
|
- ${undefined_var}
|
||||||
- $undefined_var
|
- $undefined_var
|
||||||
- ${ undefined_var }
|
- ${ undefined_var }
|
||||||
|
- key1: 1
|
||||||
|
key2: 2
|
||||||
|
@ -19,3 +19,5 @@ test_list:
|
|||||||
- ${undefined_var}
|
- ${undefined_var}
|
||||||
- $undefined_var
|
- $undefined_var
|
||||||
- ${ undefined_var }
|
- ${ undefined_var }
|
||||||
|
- key${var1}: 1
|
||||||
|
key${var2}: 2
|
||||||
|
@ -6,6 +6,7 @@ package_result:
|
|||||||
root file
|
root file
|
||||||
- Double substitution also works; the value of var7 is 79, where A is a package
|
- Double substitution also works; the value of var7 is 79, where A is a package
|
||||||
var
|
var
|
||||||
|
- key79: Key should substitute to key79
|
||||||
local_results:
|
local_results:
|
||||||
- The value of B is 5
|
- The value of B is 5
|
||||||
- 'You will see, however, that
|
- 'You will see, however, that
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
package_result:
|
package_result:
|
||||||
- The value of A*B is ${A * B}, where A is a package var and B is a substitution in the root file
|
- The value of A*B is ${A * B}, where A is a package var and B is a substitution in the root file
|
||||||
- Double substitution also works; the value of var7 is ${var$A}, where A is a package var
|
- Double substitution also works; the value of var7 is ${var$A}, where A is a package var
|
||||||
|
- key${var7}: Key should substitute to key79
|
||||||
|
Loading…
x
Reference in New Issue
Block a user