mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 13:56:10 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff36d3a6c8 | ||
|
|
ba3bc70bbb | ||
|
|
6c1a7642f3 | ||
|
|
8b0d1d31db | ||
|
|
653686e11e | ||
|
|
d17dcaaee4 | ||
|
|
1ce0db00b3 | ||
|
|
6409611e1c | ||
|
|
86dcbe4645 | ||
|
|
a50a30414e |
@@ -1007,19 +1007,19 @@ jobs:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 S3
|
||||
# yamllint disable-line rule:line-length
|
||||
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep SOC_RISCV_COPROC_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC --grep SOC_I2S_SUPPORTS_TDM --grep USE_OPENTHREAD
|
||||
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC --grep SOC_I2S_SUPPORTS_TDM --grep USE_OPENTHREAD
|
||||
tidy_environment: esp32s3-idf-tidy
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 P4
|
||||
# P4 has no native Wi-Fi/BLE; those run over the hosted co-processor,
|
||||
# so their code paths differ -- lint them under the P4 build too.
|
||||
# yamllint disable-line rule:line-length
|
||||
options: --environment esp32p4-idf-tidy --grep USE_ESP32_VARIANT_ESP32P4 --grep SOC_LP_CORE_SUPPORTED --grep CONFIG_IDF_TARGET_ESP32P4 --grep USE_ESP32_HOSTED --grep USE_WIFI --grep USE_BLE
|
||||
options: --environment esp32p4-idf-tidy --grep USE_ESP32_VARIANT_ESP32P4 --grep CONFIG_IDF_TARGET_ESP32P4 --grep USE_ESP32_HOSTED --grep USE_WIFI --grep USE_BLE
|
||||
tidy_environment: esp32p4-idf-tidy
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 C6
|
||||
# yamllint disable-line rule:line-length
|
||||
options: --environment esp32c6-idf-tidy --grep SOC_LP_I2C_SUPPORTED --grep SOC_LP_CORE_SUPPORTED --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
|
||||
options: --environment esp32c6-idf-tidy --grep SOC_LP_I2C_SUPPORTED --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
|
||||
tidy_environment: esp32c6-idf-tidy
|
||||
|
||||
steps:
|
||||
|
||||
@@ -38,7 +38,7 @@ repos:
|
||||
- id: yamllint
|
||||
exclude: ^(\.clang-format|\.clang-tidy)$
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v22.1.8
|
||||
rev: v13.0.1
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c, c++]
|
||||
|
||||
@@ -205,9 +205,8 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
for a table of structs copy the whole entry out and return the copy, never a pointer into flash.
|
||||
On other platforms they are a plain `memcpy` and dereference, so no `#ifdef` is needed. Declare log tags with
|
||||
`ESPHOME_LOG_TAG(TAG, "name")`, pass status text as `LOG_STR`, e.g.
|
||||
`this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL))`. An action that only forwards a byte payload is
|
||||
registered with `automation.ApplyBytes` (see Actions below), which keeps a constant payload in flash;
|
||||
`TEMPLATABLE_BYTES` is for a `play()` that does more with the bytes.
|
||||
`this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL))`, and give an action whose payload is a byte array
|
||||
a `TEMPLATABLE_BYTES` field so a static code stays in flash.
|
||||
* **Fixed sets of names** (status words, model names): a `PROGMEM_STRING_TABLE` (`esphome/core/progmem.h`),
|
||||
logged with `get_log_str` and `LOG_STR_ARG`, copied out with `ESPHOME_strncpy_P` before `publish_state`.
|
||||
A `dump_config` helper that picks a name returns `const LogString *` from `LOG_STR(...)`.
|
||||
@@ -605,9 +604,7 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
)
|
||||
```
|
||||
The `ApplyField`, `ApplyCall` and `register_apply_action` docstrings in `esphome/automation.py` cover
|
||||
the rest; `cover.control` and `cover.template.publish` are in-tree examples. An action whose input is a
|
||||
byte payload passes `automation.ApplyBytes(CONF_DATA, "send({})")` in the same call; a constant payload
|
||||
stays in flash and a lambda's vector is forwarded as data and size. `TEMPLATABLE_VALUE` with
|
||||
the rest; `cover.control` and `cover.template.publish` are in-tree examples. `TEMPLATABLE_VALUE` with
|
||||
`cg.templatable` stays for actions whose `play()` has real logic beyond forwarding values.
|
||||
|
||||
* **Conditions:**
|
||||
|
||||
@@ -205,7 +205,6 @@ esphome/components/esp32_hosted/update/* @swoboda1337
|
||||
esphome/components/esp32_power_management/* @rwrozelle
|
||||
esphome/components/esp32_rmt/* @jesserockz
|
||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||
esphome/components/esp32_ulp/* @swoboda1337
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/esp_ldo/* @clydebarrow
|
||||
esphome/components/espectre/* @francescopace
|
||||
|
||||
+14
-107
@@ -59,15 +59,10 @@ async def templatable_bytes(
|
||||
if cg.is_template(value):
|
||||
fn = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(set_template(fn))
|
||||
elif len(value) > 0xFFFF:
|
||||
raise EsphomeError(f"Byte payload is {len(value)} bytes; the maximum is 65535")
|
||||
else:
|
||||
cg.add(set_static(*_static_bytes(table_name, value)))
|
||||
|
||||
|
||||
def _static_bytes(table_name: str, value: bytes | list[int]) -> tuple[MockObj, int]:
|
||||
"""The shared PROGMEM table for a constant payload and its length."""
|
||||
if (size := len(value)) > 0xFFFF:
|
||||
raise EsphomeError(f"Byte payload is {size} bytes; the maximum is 65535")
|
||||
return progmem_bytes(table_name, value), size
|
||||
cg.add(set_static(progmem_bytes(table_name, value), len(value)))
|
||||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
@@ -361,33 +356,6 @@ class ApplyField:
|
||||
return ApplyCall(target, ((self.conf_key, self.type_, self.const_fn),))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplyBytes:
|
||||
"""A byte payload key forwarded as ``target``, whose ``{}`` stands for ``data, len``.
|
||||
|
||||
A constant payload is a shared PROGMEM table passed as is; on ESP8266 it is first copied
|
||||
out, to the stack up to ``stack`` bytes and to the heap beyond, unless ``progmem_target``
|
||||
names a call that reads flash itself. A lambda's ``std::vector<uint8_t>`` is passed as
|
||||
its data and size. The template may name the parent as ``{parent}``. An absent key
|
||||
emits nothing.
|
||||
"""
|
||||
|
||||
conf_key: str | tuple[str, ...]
|
||||
target: str
|
||||
stack: int = 64
|
||||
progmem_target: str | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
for target in (self.target, self.progmem_target):
|
||||
if target is None:
|
||||
continue
|
||||
fields = [
|
||||
f for _, f, _, _ in string.Formatter().parse(target) if f is not None
|
||||
]
|
||||
if fields.count("") != 1 or any(f not in ("", "parent") for f in fields):
|
||||
raise ValueError(f"apply bytes target {target!r} needs one {{}}")
|
||||
|
||||
|
||||
def _config_lookup(config: ConfigType, key: str | tuple[str, ...]) -> Any:
|
||||
if isinstance(key, str):
|
||||
return config.get(key)
|
||||
@@ -520,7 +488,7 @@ def _apply_values(config: ConfigType, members: list[tuple[Any, Any, Any]]) -> li
|
||||
def register_apply_action(
|
||||
name: str,
|
||||
schema: cv.Schema,
|
||||
*fields: ApplyField | ApplyCall | ApplyBytes,
|
||||
*fields: ApplyField | ApplyCall,
|
||||
call: str | None = None,
|
||||
id_key: str = CONF_ID,
|
||||
) -> None:
|
||||
@@ -536,7 +504,7 @@ def register_apply_action(
|
||||
def apply_action_builder(
|
||||
name: str,
|
||||
schema: cv.Schema,
|
||||
*fields: ApplyField | ApplyCall | ApplyBytes,
|
||||
*fields: ApplyField | ApplyCall,
|
||||
call: str | None = None,
|
||||
id_key: str = CONF_ID,
|
||||
) -> Callable[..., Awaitable[MockObj]]:
|
||||
@@ -549,20 +517,17 @@ def apply_action_builder(
|
||||
and ``apply_call.perform()`` is appended.
|
||||
"""
|
||||
# An action stores the value, so a std::string constant stays in flash on ESP8266.
|
||||
statements_spec = [
|
||||
(
|
||||
c.target,
|
||||
_default_std_string(c.members, flash_string),
|
||||
)
|
||||
for c in (f if isinstance(f, ApplyCall) else f.call() for f in fields)
|
||||
]
|
||||
_check_key_in_schema(name, schema, id_key)
|
||||
statements_spec: list[ApplyBytes | tuple[str, list[tuple[Any, Any, Any]]]] = []
|
||||
for f in fields:
|
||||
if isinstance(f, ApplyBytes):
|
||||
_check_key_in_schema(name, schema, f.conf_key)
|
||||
statements_spec.append(f)
|
||||
continue
|
||||
c = f if isinstance(f, ApplyCall) else f.call()
|
||||
members = _default_std_string(c.members, flash_string)
|
||||
for _, members in statements_spec:
|
||||
for conf_key, _, _ in members:
|
||||
_check_key_in_schema(name, schema, conf_key)
|
||||
statements_spec.append((c.target, members))
|
||||
# Named like the generated function, so it cannot clash with an SDK symbol
|
||||
table_name = f"esphome__{name.replace('.', '_')}__bytes"
|
||||
|
||||
async def builder(
|
||||
config: ConfigType,
|
||||
@@ -574,15 +539,7 @@ def apply_action_builder(
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
receiver = "apply_call." if call else f"{parent}->"
|
||||
statements: list[str] = []
|
||||
for spec in statements_spec:
|
||||
if isinstance(spec, ApplyBytes):
|
||||
statements.extend(
|
||||
await _render_bytes(
|
||||
spec, config, receiver, parent, lambda_args, table_name
|
||||
)
|
||||
)
|
||||
continue
|
||||
target, members = spec
|
||||
for target, members in statements_spec:
|
||||
values = _apply_values(config, members)
|
||||
if not _apply_call_active(members, values):
|
||||
continue
|
||||
@@ -603,56 +560,6 @@ def apply_action_builder(
|
||||
return builder
|
||||
|
||||
|
||||
async def _render_bytes(
|
||||
spec: ApplyBytes,
|
||||
config: ConfigType,
|
||||
receiver: str,
|
||||
parent: str,
|
||||
lambda_args: TemplateArgsType,
|
||||
table_name: str,
|
||||
) -> list[str]:
|
||||
"""The statements for one ``ApplyBytes``: what ``TemplatableBytes::visit`` does, inlined."""
|
||||
value = _config_lookup(config, spec.conf_key)
|
||||
if value is None:
|
||||
return []
|
||||
|
||||
# The temporary carries the generated-name prefix so a trigger argument cannot shadow it
|
||||
def send(target: str, data: str) -> str:
|
||||
return f"{receiver}{target.format(data, parent=parent)};"
|
||||
|
||||
if isinstance(value, Lambda):
|
||||
inner = await cg.process_lambda(
|
||||
value, lambda_args, return_type=cg.std_vector.template(cg.uint8)
|
||||
)
|
||||
return [
|
||||
"{",
|
||||
f" const std::vector<uint8_t> esphome__bytes = ({call_lambda(inner)});",
|
||||
f" {send(spec.target, 'esphome__bytes.data(), esphome__bytes.size()')}",
|
||||
"}",
|
||||
]
|
||||
table, size = _static_bytes(table_name, value)
|
||||
# Qualified so a trigger argument cannot shadow a reused table; an empty payload is nullptr
|
||||
args = f"::{table}, {size}" if size else f"{table}, {size}"
|
||||
if CORE.is_esp8266 and spec.progmem_target is not None:
|
||||
return [send(spec.progmem_target, args)]
|
||||
if not CORE.is_esp8266:
|
||||
return [send(spec.target, args)]
|
||||
# A PROGMEM table must be read with aligned loads on ESP8266, so copy it out first;
|
||||
# the length is known here, so the copy is an exact array, on the heap past ``stack``.
|
||||
# An empty payload still gets a one byte array: the raw lwIP socket rejects a null buffer.
|
||||
if size <= spec.stack:
|
||||
buffer, data = f"uint8_t esphome__bytes[{max(size, 1)}];", "esphome__bytes"
|
||||
else:
|
||||
buffer, data = (
|
||||
f"auto esphome__bytes = std::make_unique<uint8_t[]>({size});",
|
||||
"esphome__bytes.get()",
|
||||
)
|
||||
statements = ["{", f" {buffer}"]
|
||||
if size:
|
||||
statements.append(f" esphome::progmem_memcpy({data}, {args});")
|
||||
return [*statements, f" {send(spec.target, f'{data}, {size}')}", "}"]
|
||||
|
||||
|
||||
def _apply_call_active(members: list[tuple[Any, Any, Any]], values: list[Any]) -> bool:
|
||||
"""An ``ApplyCall`` is emitted unless it has keys and none of them is set."""
|
||||
return not members or any(value is not None for value in values)
|
||||
|
||||
@@ -8,14 +8,12 @@ import textwrap
|
||||
|
||||
from esphome.build_helpers import pch
|
||||
from esphome.components.esp32 import (
|
||||
KEY_SRC_CMAKE,
|
||||
get_esp32_variant,
|
||||
get_excluded_builtin_components,
|
||||
get_managed_component_require_names,
|
||||
idf_version,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import KEY_ESP32
|
||||
from esphome.core import CORE
|
||||
from esphome.espidf import variant_to_idf_target
|
||||
from esphome.framework_helpers import (
|
||||
@@ -462,7 +460,6 @@ def get_component_cmakelists() -> str:
|
||||
# get_project_cmakelists so they reach every component, not just src/.
|
||||
link_opts = get_project_link_flags()
|
||||
link_opts_str = "\n ".join(link_opts) if link_opts else ""
|
||||
src_cmake = "\n".join(CORE.data[KEY_ESP32].get(KEY_SRC_CMAKE, []))
|
||||
|
||||
return f"""\
|
||||
# Auto-generated by ESPHome
|
||||
@@ -508,7 +505,6 @@ idf_component_register(
|
||||
target_link_options(${{COMPONENT_LIB}} PUBLIC
|
||||
{link_opts_str}
|
||||
)
|
||||
{src_cmake}
|
||||
{_pch_cmake_block()}"""
|
||||
|
||||
|
||||
|
||||
@@ -94,11 +94,11 @@ void ADCSensor::setup() {
|
||||
}
|
||||
#elif defined(ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED)
|
||||
adc_cali_line_fitting_config_t cali_config = {
|
||||
.unit_id = this->adc_unit_,
|
||||
.atten = this->attenuation_,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.unit_id = this->adc_unit_,
|
||||
.atten = this->attenuation_,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
|
||||
.default_vref = 1100, // Default reference voltage in mV
|
||||
.default_vref = 1100, // Default reference voltage in mV
|
||||
#endif // !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
|
||||
};
|
||||
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||
@@ -248,11 +248,11 @@ float ADCSensor::sample_autorange_() {
|
||||
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||
#elif defined(ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED)
|
||||
adc_cali_line_fitting_config_t cali_config = {
|
||||
.unit_id = this->adc_unit_,
|
||||
.atten = atten,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.unit_id = this->adc_unit_,
|
||||
.atten = atten,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
|
||||
.default_vref = 1100,
|
||||
.default_vref = 1100,
|
||||
#endif
|
||||
};
|
||||
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||
|
||||
@@ -7,7 +7,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_DESCRIPTION, CONF_HOST
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.components.network import require_ipv4, require_ipv6
|
||||
from esphome.components.network import DOMAIN as NETWORK_DOMAIN
|
||||
|
||||
# ENCRYPTION_SCHEMA and validate_encryption_key are re-exported for external
|
||||
# components and downstream consumers that import them from api
|
||||
@@ -28,6 +28,7 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_DELAY,
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_ENCRYPTION,
|
||||
CONF_EVENT,
|
||||
CONF_ID,
|
||||
@@ -51,6 +52,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_priority
|
||||
from esphome.cpp_generator import Expression, MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import fnv1_hash
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigFragmentType, ConfigType
|
||||
@@ -331,10 +333,7 @@ def _validate_outgoing_host(value: str) -> IPv4Address | IPv6Address:
|
||||
"give the address without the '%' part"
|
||||
)
|
||||
if (mapped := address.ipv4_mapped) is not None:
|
||||
address = mapped
|
||||
# The build must carry the host's address family for the dial to work
|
||||
require = require_ipv6 if isinstance(address, IPv6Address) else require_ipv4
|
||||
require("api.outgoing_connection")(value)
|
||||
return mapped
|
||||
return address
|
||||
|
||||
|
||||
@@ -480,12 +479,31 @@ def _action_strings(conf: ConfigType, has_metadata: bool) -> list[str | None]:
|
||||
return strings
|
||||
|
||||
|
||||
def _validate_outgoing_host_ipv6(config: ConfigType) -> ConfigType:
|
||||
"""An IPv6 host can never be parsed, so never dialed, without IPv6."""
|
||||
if (
|
||||
(outgoing := config.get(CONF_OUTGOING_CONNECTION)) is None
|
||||
or (host := outgoing.get(CONF_HOST)) is None
|
||||
or host.version != 6
|
||||
):
|
||||
return config
|
||||
network_conf = fv.full_config.get().get(NETWORK_DOMAIN) or {}
|
||||
if not network_conf.get(CONF_ENABLE_IPV6):
|
||||
raise cv.Invalid(
|
||||
"outgoing_connection host is an IPv6 address but IPv6 is not "
|
||||
"enabled; set 'network: enable_ipv6: true'",
|
||||
path=[CONF_OUTGOING_CONNECTION, CONF_HOST],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _validate_wizard(config: ConfigType) -> ConfigType:
|
||||
wizard.final_validate(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
_validate_outgoing_host_ipv6,
|
||||
_validate_wizard,
|
||||
)
|
||||
|
||||
|
||||
@@ -683,7 +683,7 @@ message ListEntitiesFanResponse {
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63];
|
||||
EntityCategory entity_category = 11;
|
||||
repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "ConstVector<const char *, true>"];
|
||||
repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
// Deprecated in API version 1.6 - only used in deprecated fields
|
||||
@@ -1331,9 +1331,9 @@ message ListEntitiesClimateResponse {
|
||||
bool supports_action = 12; // Deprecated: use feature_flags
|
||||
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"];
|
||||
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
|
||||
repeated string supported_custom_fan_modes = 15 [(container_pointer_no_template) = "ConstVector<const char *, true>"];
|
||||
repeated string supported_custom_fan_modes = 15 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||
repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
|
||||
repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "ConstVector<const char *, true>"];
|
||||
repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "std::vector<const char *>"];
|
||||
bool disabled_by_default = 18;
|
||||
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63];
|
||||
EntityCategory entity_category = 20;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace esphome::api {
|
||||
|
||||
// uncomment to log raw packets
|
||||
// #define HELPER_LOG_PACKETS
|
||||
//#define HELPER_LOG_PACKETS
|
||||
|
||||
// Maximum message size limits to prevent OOM on constrained devices
|
||||
// Handshake messages are limited to a small size for security
|
||||
|
||||
@@ -38,14 +38,12 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
const auto *bytes = reinterpret_cast<const uint8_t *>(&addr6->sin6_addr);
|
||||
uint32_t prefix[3];
|
||||
memcpy(prefix, bytes, sizeof(prefix));
|
||||
#if USE_NETWORK_IPV4
|
||||
// A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d
|
||||
if (prefix[0] == 0 && prefix[1] == 0 && prefix[2] == htonl(0xFFFFUL)) {
|
||||
target.family = AF_INET;
|
||||
memcpy(target.addr, bytes + sizeof(prefix), sizeof(struct in_addr));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// A link-local target is only reachable through the interface it came in
|
||||
// on. Device platforms number interfaces from one; a host build can hand
|
||||
// out an index too large to store, and a truncated one dials the wrong
|
||||
@@ -59,7 +57,6 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV4
|
||||
if (family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
@@ -67,9 +64,6 @@ static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) {
|
||||
target.family = AF_INET;
|
||||
memcpy(target.addr, &addr4->sin_addr, sizeof(addr4->sin_addr));
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -91,7 +85,6 @@ socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *a
|
||||
return sizeof(*addr6);
|
||||
}
|
||||
#endif
|
||||
#if USE_NETWORK_IPV4
|
||||
if (this->saved_.family != AF_INET) {
|
||||
return 0;
|
||||
}
|
||||
@@ -101,9 +94,6 @@ socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *a
|
||||
addr4->sin_port = htons(API_OUTGOING_CONNECTION_PORT);
|
||||
memcpy(&addr4->sin_addr, this->saved_.addr, sizeof(addr4->sin_addr));
|
||||
return sizeof(*addr4);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -924,7 +924,8 @@ uint32_t ListEntitiesSensorResponse::calc_size_msg(const void *self) {
|
||||
return size;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *SensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
SensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const SensorStateResponse *>(self);
|
||||
pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key);
|
||||
if (uint32_t raw = float_to_raw(msg.state); raw != 0) [[likely]] {
|
||||
@@ -937,7 +938,8 @@ uint8_t *SensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t SensorStateResponse::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
SensorStateResponse::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const SensorStateResponse *>(self);
|
||||
uint32_t size = 0;
|
||||
size += 5;
|
||||
@@ -1094,7 +1096,8 @@ void SubscribeLogsRequest::decode_field(void *self, uint32_t tag, const uint8_t
|
||||
}
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *SubscribeLogsResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
SubscribeLogsResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const SubscribeLogsResponse *>(self);
|
||||
pos = ProtoEncode::encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(msg.level));
|
||||
pos = ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 26);
|
||||
@@ -1103,7 +1106,8 @@ uint8_t *SubscribeLogsResponse::encode_msg(const void *self, uint8_t *__restrict
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t SubscribeLogsResponse::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
SubscribeLogsResponse::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const SubscribeLogsResponse *>(self);
|
||||
uint32_t size = 0;
|
||||
size += 2;
|
||||
@@ -2444,8 +2448,8 @@ void SubscribeBluetoothLEAdvertisementsRequest::decode_field(void *self, uint32_
|
||||
}
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *BluetoothLERawAdvertisementsResponse::encode_msg(const void *self,
|
||||
uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
BluetoothLERawAdvertisementsResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const BluetoothLERawAdvertisementsResponse *>(self);
|
||||
for (uint16_t i = 0; i < msg.advertisements_len; i++) {
|
||||
auto &sub_msg = msg.advertisements[i];
|
||||
@@ -2468,7 +2472,8 @@ uint8_t *BluetoothLERawAdvertisementsResponse::encode_msg(const void *self,
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t BluetoothLERawAdvertisementsResponse::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
BluetoothLERawAdvertisementsResponse::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const BluetoothLERawAdvertisementsResponse *>(self);
|
||||
uint32_t size = 0;
|
||||
for (uint16_t i = 0; i < msg.advertisements_len; i++) {
|
||||
@@ -3815,13 +3820,15 @@ void ZWaveProxyFrame::decode_field(void *self, uint32_t tag, const uint8_t *data
|
||||
}
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *ZWaveProxyFrame::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
ZWaveProxyFrame::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const ZWaveProxyFrame *>(self);
|
||||
pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.data, msg.data_len);
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t ZWaveProxyFrame::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
ZWaveProxyFrame::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const ZWaveProxyFrame *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, msg.data_len);
|
||||
@@ -3936,7 +3943,8 @@ void InfraredRFTransmitRawTimingsRequest::decode_field(void *self, uint32_t tag,
|
||||
}
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *InfraredRFReceiveEvent::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
InfraredRFReceiveEvent::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const InfraredRFReceiveEvent *>(self);
|
||||
#ifdef USE_DEVICES
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.device_id);
|
||||
@@ -3948,7 +3956,8 @@ uint8_t *InfraredRFReceiveEvent::encode_msg(const void *self, uint8_t *__restric
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t InfraredRFReceiveEvent::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
InfraredRFReceiveEvent::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const InfraredRFReceiveEvent *>(self);
|
||||
uint32_t size = 0;
|
||||
#ifdef USE_DEVICES
|
||||
@@ -4052,14 +4061,16 @@ void SerialProxyConfigureRequest::decode_field(void *self, uint32_t tag, const u
|
||||
}
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint8_t *SerialProxyDataReceived::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
uint8_t *
|
||||
SerialProxyDataReceived::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {
|
||||
const auto &msg = *static_cast<const SerialProxyDataReceived *>(self);
|
||||
pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.instance);
|
||||
pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.data_ptr_, msg.data_len_);
|
||||
return pos;
|
||||
}
|
||||
__attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes)
|
||||
uint32_t SerialProxyDataReceived::calc_size_msg(const void *self) {
|
||||
uint32_t
|
||||
SerialProxyDataReceived::calc_size_msg(const void *self) {
|
||||
const auto &msg = *static_cast<const SerialProxyDataReceived *>(self);
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, msg.instance);
|
||||
|
||||
@@ -984,7 +984,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_speed{false};
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
const ConstVector<const char *, true> *supported_preset_modes{};
|
||||
const std::vector<const char *> *supported_preset_modes{};
|
||||
static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM);
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);
|
||||
@@ -1777,9 +1777,9 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_action{false};
|
||||
const climate::ClimateFanModeMask *supported_fan_modes{};
|
||||
const climate::ClimateSwingModeMask *supported_swing_modes{};
|
||||
const ConstVector<const char *, true> *supported_custom_fan_modes{};
|
||||
const std::vector<const char *> *supported_custom_fan_modes{};
|
||||
const climate::ClimatePresetMask *supported_presets{};
|
||||
const ConstVector<const char *, true> *supported_custom_presets{};
|
||||
const std::vector<const char *> *supported_custom_presets{};
|
||||
float visual_current_temperature_step{0.0f};
|
||||
bool supports_current_humidity{false};
|
||||
bool supports_target_humidity{false};
|
||||
|
||||
@@ -19,242 +19,242 @@ class APIServerConnectionBase {
|
||||
public:
|
||||
#endif
|
||||
|
||||
void on_hello_request(const HelloRequest &value) {};
|
||||
void on_hello_request(const HelloRequest &value){};
|
||||
|
||||
void on_disconnect_request(const DisconnectRequest &value) {};
|
||||
void on_disconnect_response() {};
|
||||
void on_ping_request() {};
|
||||
void on_ping_response() {};
|
||||
void on_device_info_request() {};
|
||||
void on_disconnect_request(const DisconnectRequest &value){};
|
||||
void on_disconnect_response(){};
|
||||
void on_ping_request(){};
|
||||
void on_ping_response(){};
|
||||
void on_device_info_request(){};
|
||||
|
||||
void on_device_capabilities_request() {};
|
||||
void on_device_capabilities_request(){};
|
||||
|
||||
#ifdef USE_API_WIZARD
|
||||
void on_device_wizard_request() {};
|
||||
void on_device_wizard_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_WIZARD_INPUTS
|
||||
void on_wizard_input_set_request(const WizardInputSetRequest &value) {};
|
||||
void on_wizard_input_set_request(const WizardInputSetRequest &value){};
|
||||
#endif
|
||||
void on_list_entities_request() {};
|
||||
void on_list_entities_request(){};
|
||||
|
||||
void on_subscribe_states_request() {};
|
||||
void on_subscribe_states_request(){};
|
||||
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &value) {};
|
||||
void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request(const FanCommandRequest &value) {};
|
||||
void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request(const LightCommandRequest &value) {};
|
||||
void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request(const SwitchCommandRequest &value) {};
|
||||
void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &value) {};
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value) {};
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void on_subscribe_homeassistant_services_request() {};
|
||||
void on_subscribe_homeassistant_services_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &value) {};
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_subscribe_home_assistant_states_request() {};
|
||||
void on_subscribe_home_assistant_states_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &value) {};
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
#endif
|
||||
|
||||
void on_get_time_response(const GetTimeResponse &value) {};
|
||||
void on_get_time_response(const GetTimeResponse &value){};
|
||||
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void on_execute_service_request(const ExecuteServiceRequest &value) {};
|
||||
void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void on_camera_image_request(const CameraImageRequest &value) {};
|
||||
void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request(const ClimateCommandRequest &value) {};
|
||||
void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &value) {};
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void on_number_command_request(const NumberCommandRequest &value) {};
|
||||
void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void on_select_command_request(const SelectCommandRequest &value) {};
|
||||
void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIREN
|
||||
void on_siren_command_request(const SirenCommandRequest &value) {};
|
||||
void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_command_request(const LockCommandRequest &value) {};
|
||||
void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &value) {};
|
||||
void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &value) {};
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value) {};
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &value) {};
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value) {};
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value) {};
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value) {};
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value) {};
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value) {};
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value) {};
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_subscribe_bluetooth_connections_free_request() {};
|
||||
void on_subscribe_bluetooth_connections_free_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request() {};
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value) {};
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value) {};
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &value) {};
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value) {};
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &value) {};
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value) {};
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value) {};
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value) {};
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value) {};
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value) {};
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void on_text_command_request(const TextCommandRequest &value) {};
|
||||
void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void on_date_command_request(const DateCommandRequest &value) {};
|
||||
void on_date_command_request(const DateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void on_time_command_request(const TimeCommandRequest &value) {};
|
||||
void on_time_command_request(const TimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
void on_valve_command_request(const ValveCommandRequest &value) {};
|
||||
void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &value) {};
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
void on_update_command_request(const UpdateCommandRequest &value) {};
|
||||
void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &value) {};
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &value) {};
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value) {};
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value) {};
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &value) {};
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value) {};
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value) {};
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_request(const SerialProxyRequest &value) {};
|
||||
void on_serial_proxy_request(const SerialProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &value) {};
|
||||
void on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_subscribe_serial_proxy_identity_request() {};
|
||||
void on_subscribe_serial_proxy_identity_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value) {};
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENDSPIN
|
||||
void on_sendspin_pairing_token_request() {};
|
||||
void on_sendspin_pairing_token_request(){};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -593,10 +593,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef
|
||||
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
|
||||
std::function<void(StringRef)> &&f, bool once) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = entity_id,
|
||||
.attribute = attribute,
|
||||
.callback = std::move(f),
|
||||
.once = once,
|
||||
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
|
||||
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ namespace esphome::api {
|
||||
|
||||
// Converts a lambda result to the string sent to Home Assistant
|
||||
template<typename T>
|
||||
requires(!std::is_pointer_v<std::remove_cvref_t<T>>)
|
||||
std::string field_to_string(T &&val) {
|
||||
requires(!std::is_pointer_v<std::remove_cvref_t<T>>) std::string field_to_string(T &&val) {
|
||||
return to_string(std::forward<T>(val)); // NOLINT
|
||||
}
|
||||
inline std::string field_to_string(const char *val) { return val ? std::string(val) : std::string(); }
|
||||
|
||||
@@ -26,17 +26,21 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}};
|
||||
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||
// clang-format off
|
||||
{0.0f, 15.1f},
|
||||
{15.1f, 30.1f},
|
||||
{30.1f, 55.1f},
|
||||
{55.1f, 110.1f},
|
||||
{55.1f, 110.1f}
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
||||
// clang-format off
|
||||
{0.0f, 25.1f},
|
||||
{25.1f, 50.1f},
|
||||
{50.1f, 90.1f},
|
||||
{90.1f, 180.1f},
|
||||
{90.1f, 180.1f}
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
|
||||
@@ -157,7 +157,7 @@ class ATM90E32Component final : public PollingComponent,
|
||||
#ifdef USE_NUMBER
|
||||
return (phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f; // Default current
|
||||
#else
|
||||
return 5.0f; // Default current
|
||||
return 5.0f; // Default current
|
||||
#endif
|
||||
}
|
||||
bool using_saved_calibrations_ = false; // Track if stored calibrations are being used
|
||||
|
||||
@@ -22,7 +22,7 @@ class AudioStreamInfo {
|
||||
*/
|
||||
public:
|
||||
AudioStreamInfo()
|
||||
: AudioStreamInfo(16, 1, 16000) {}; // Default values represent ESPHome's audio components historical values
|
||||
: AudioStreamInfo(16, 1, 16000){}; // Default values represent ESPHome's audio components historical values
|
||||
AudioStreamInfo(uint8_t bits_per_sample, uint8_t channels, uint32_t sample_rate);
|
||||
|
||||
uint8_t get_bits_per_sample() const { return this->bits_per_sample_; }
|
||||
|
||||
@@ -92,8 +92,10 @@ enum BedjetCommand : uint8_t {
|
||||
};
|
||||
|
||||
#define BEDJET_FAN_STEP_NAMES_ \
|
||||
{"5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", \
|
||||
"55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%"}
|
||||
{ \
|
||||
"5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", "55%", "60%", "65%", "70%", "75%", "80%", \
|
||||
"85%", "90%", "95%", "100%" \
|
||||
}
|
||||
|
||||
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ void BedJetClimate::dump_config() {
|
||||
|
||||
void BedJetClimate::setup() {
|
||||
// Set custom modes once during setup — stored on Climate base class, wired via get_traits()
|
||||
this->set_supported_custom_fan_modes_static(BEDJET_FAN_STEP_NAMES, BEDJET_FAN_SPEED_COUNT);
|
||||
this->set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
|
||||
this->set_supported_custom_presets({
|
||||
this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT",
|
||||
"M1",
|
||||
|
||||
@@ -20,7 +20,7 @@ class BedJetFan final : public fan::Fan, public BedJetClient, public PollingComp
|
||||
|
||||
/* BedJetClient status update */
|
||||
void on_status(const BedjetStatusPacket *data) override;
|
||||
void on_bedjet_state(bool is_ready) override {};
|
||||
void on_bedjet_state(bool is_ready) override{};
|
||||
std::string describe() override;
|
||||
|
||||
fan::FanTraits get_traits() override { return fan::FanTraits(false, true, false, BEDJET_FAN_SPEED_COUNT); }
|
||||
|
||||
@@ -20,35 +20,18 @@ extern "C" {
|
||||
|
||||
static const uint32_t SPI_TX_DMA_CHANNEL = GDMA_CHANNEL_3;
|
||||
|
||||
// TODO: Check if SPI_PERI_CLK_DCO depends on the chip variant
|
||||
static const uint32_t SPI_PERI_CLK_26M = 26000000;
|
||||
|
||||
#if defined(USE_LIBRETINY_VARIANT_BK7251)
|
||||
static const uint32_t SPI_PERI_CLK_DCO = 180000000;
|
||||
#else
|
||||
static const uint32_t SPI_PERI_CLK_DCO = 120000000;
|
||||
#endif
|
||||
|
||||
// The beken378 SDK has two SPI register layouts:
|
||||
// N-like (BK7231N, BK7238, BK7252N): CTRL +0x00, CONFIG +0x04, STAT +0x08, DAT +0x0C
|
||||
// T-like (BK7231T, BK7251): CTRL +0x00, STAT +0x04, DAT +0x08
|
||||
// The N-like variants also have TX enable / transfer length / TX finish bits in CONFIG.
|
||||
// The T-like variants have no CONFIG address.
|
||||
#if defined(USE_LIBRETINY_VARIANT_BK7231N) || defined(USE_LIBRETINY_VARIANT_BK7238)
|
||||
#define BEKEN_SPI_IS_N_LIKE
|
||||
#endif
|
||||
|
||||
static const uint32_t SPI_BASE = 0x00802700;
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
static const uint32_t SPI_DAT = SPI_BASE + 3 * 4;
|
||||
static const uint32_t SPI_CONFIG = SPI_BASE + 1 * 4;
|
||||
|
||||
static const uint32_t SPI_TX_EN = 1 << 0;
|
||||
static const uint32_t CTRL_NSSMD_3 = 1 << 17;
|
||||
static const uint32_t SPI_TX_FINISH_EN = 1 << 2;
|
||||
static const uint32_t SPI_RX_FINISH_EN = 1 << 3;
|
||||
#else
|
||||
static const uint32_t SPI_DAT = SPI_BASE + 2 * 4;
|
||||
#endif
|
||||
|
||||
static const uint32_t CTRL_NSSMD_3 = 1 << 17;
|
||||
|
||||
namespace esphome::beken_spi_led_strip {
|
||||
|
||||
@@ -72,7 +55,6 @@ static void set_spi_ctrl_register(uint32_t bit, bool val) {
|
||||
REG_WRITE(SPI_CTRL, value);
|
||||
}
|
||||
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
static void set_spi_config_register(uint32_t bit, bool val) {
|
||||
uint32_t value = REG_READ(SPI_CONFIG);
|
||||
if (val == 0) {
|
||||
@@ -82,13 +64,10 @@ static void set_spi_config_register(uint32_t bit, bool val) {
|
||||
}
|
||||
REG_WRITE(SPI_CONFIG, value);
|
||||
}
|
||||
#endif
|
||||
|
||||
void spi_dma_tx_enable(bool enable) {
|
||||
GDMA_CFG_ST en_cfg;
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
set_spi_config_register(SPI_TX_EN, enable);
|
||||
#endif
|
||||
en_cfg.channel = SPI_TX_DMA_CHANNEL;
|
||||
en_cfg.param = enable ? 1 : 0;
|
||||
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
|
||||
@@ -121,13 +100,7 @@ static void spi_set_clock(uint32_t max_hz) {
|
||||
param = PCLK_POSI_SPI;
|
||||
sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, ¶m);
|
||||
}
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
// N-like variants: spi_clk = source_clk / (2 * div)
|
||||
div = ((source_clk >> 1) / spi_clk);
|
||||
#else
|
||||
// T-like variants: spi_clk = source_clk / (2 * (div + 1))
|
||||
div = ((source_clk >> 1) / spi_clk) - 1;
|
||||
#endif
|
||||
if (div < 2) {
|
||||
div = 2;
|
||||
} else if (div >= 255) {
|
||||
@@ -137,12 +110,7 @@ static void spi_set_clock(uint32_t max_hz) {
|
||||
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
|
||||
param |= (div << SPI_CKR_POSI);
|
||||
REG_WRITE(SPI_CTRL, param);
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
const int divisor = div;
|
||||
#else
|
||||
const int divisor = div + 1;
|
||||
#endif
|
||||
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / divisor);
|
||||
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / div);
|
||||
}
|
||||
|
||||
void spi_dma_tx_finish_callback(unsigned int param) {
|
||||
@@ -219,10 +187,8 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
|
||||
set_spi_ctrl_register(TXINT_EN, false);
|
||||
set_spi_ctrl_register(RXINT_EN, false);
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
set_spi_config_register(SPI_TX_FINISH_EN, true);
|
||||
set_spi_config_register(SPI_RX_FINISH_EN, true);
|
||||
#endif
|
||||
set_spi_ctrl_register(RXOVR_EN, false);
|
||||
set_spi_ctrl_register(TXOVR_EN, false);
|
||||
|
||||
@@ -231,17 +197,9 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
value |= (1 << 17);
|
||||
REG_WRITE(SPI_CTRL, value);
|
||||
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
value = GFUNC_MODE_SPI_DMA;
|
||||
#else
|
||||
// For T-like variants, GFUNC_MODE_SPI_DMA identifies an SPI-DMA peripheral
|
||||
// with a different base address. We want the regular SPI controller here.
|
||||
value = GFUNC_MODE_SPI;
|
||||
#endif
|
||||
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
|
||||
#endif
|
||||
|
||||
GDMA_CFG_ST en_cfg;
|
||||
GDMACFG_TPYES_ST init_cfg;
|
||||
@@ -274,16 +232,10 @@ void BekenSPILEDStripLightOutput::setup() {
|
||||
|
||||
spi_dma_tx_enable(false);
|
||||
|
||||
#ifdef BEKEN_SPI_IS_N_LIKE
|
||||
// N-like variants: The SPI controller must be told how many bytes to clock out.
|
||||
value = REG_READ(SPI_CONFIG);
|
||||
value &= ~(0xFFF << 8);
|
||||
value |= ((dma_buffer_size & 0xFFF) << 8);
|
||||
REG_WRITE(SPI_CONFIG, value);
|
||||
#else
|
||||
// T-like variants: No transfer-length register. The SPI controller shifts out
|
||||
// everything the DMA writes into SPI_DAT.
|
||||
#endif
|
||||
}
|
||||
|
||||
void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) {
|
||||
@@ -313,35 +265,13 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
|
||||
}
|
||||
|
||||
if (!spi_data->first_run && !xSemaphoreTake(spi_data->dma_tx_semaphore, 10 / portTICK_PERIOD_MS)) {
|
||||
// Recover so the next update can try again instead of being stuck forever.
|
||||
ESP_LOGE(TAG, "Timed out waiting for semaphore");
|
||||
spi_dma_tx_enable(false);
|
||||
spi_data->tx_in_progress = false;
|
||||
xSemaphoreGive(spi_data->dma_tx_semaphore);
|
||||
this->status_set_warning();
|
||||
this->schedule_show();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef BEKEN_SPI_IS_N_LIKE
|
||||
if (!spi_data->first_run) {
|
||||
// For T-like variants, the GDMA finish interrupt only means that all bytes were written into
|
||||
// the SPI TX FIFO. We must still wait for it to drain before refilling it.
|
||||
uint32_t wait_start = micros();
|
||||
while (!(REG_READ(SPI_STAT) & TXFIFO_EMPTY)) {
|
||||
if (micros() - wait_start > 2000) {
|
||||
ESP_LOGW(TAG, "SPI TX FIFO did not drain in time");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (spi_data->tx_in_progress) {
|
||||
ESP_LOGE(TAG, "tx_in_progress is set");
|
||||
xSemaphoreGive(spi_data->dma_tx_semaphore);
|
||||
this->status_set_warning();
|
||||
this->schedule_show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,19 @@ class LEDStripTimings:
|
||||
spi_frequency: int
|
||||
|
||||
|
||||
# The SPI clock divider depends on the chip family and is calculated in led_strip.cpp.
|
||||
CHIPSETS = {
|
||||
"WS2812": LEDStripTimings(0b11100000, 0b11111100, 6666666),
|
||||
"SK6812": LEDStripTimings(0b11000000, 0b11111000, 7500000),
|
||||
"APA106": LEDStripTimings(0b11000000, 0b11111110, 5454545),
|
||||
"SM16703": LEDStripTimings(0b11000000, 0b11111110, 7500000),
|
||||
"WS2812": LEDStripTimings(
|
||||
0b11100000, 0b11111100, 6666666
|
||||
), # Clock divider: 9, Bit time: 1350ns
|
||||
"SK6812": LEDStripTimings(
|
||||
0b11000000, 0b11111000, 7500000
|
||||
), # Clock divider: 8, Bit time: 1200ns
|
||||
"APA106": LEDStripTimings(
|
||||
0b11000000, 0b11111110, 5454545
|
||||
), # Clock divider: 11, Bit time: 1650ns
|
||||
"SM16703": LEDStripTimings(
|
||||
0b11000000, 0b11111110, 7500000
|
||||
), # Clock divider: 8, Bit time: 1200ns
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_BH1745_ID, BH1745Component, bh1745_ns
|
||||
from .. import CONF_BH1745_ID, BH1745Component
|
||||
|
||||
DEPENDENCIES = ["bh1745"]
|
||||
|
||||
BH1745InterruptPinOutput = bh1745_ns.class_(
|
||||
"BH1745InterruptPinOutput", output.BinaryOutput
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BH1745InterruptPinOutput),
|
||||
cv.GenerateID(): cv.declare_id(output.ApplyBinaryOutput),
|
||||
cv.GenerateID(CONF_BH1745_ID): cv.use_id(BH1745Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
hub = await cg.get_variable(config[CONF_BH1745_ID])
|
||||
await cg.register_parented(var, hub)
|
||||
await output.sub_binary_outputs(config, parent=hub).create(
|
||||
config, None, apply="set_interrupt_state(state)"
|
||||
)
|
||||
cg.add(hub.enable_interrupt_output())
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "../bh1745.h"
|
||||
|
||||
namespace esphome::bh1745 {
|
||||
|
||||
/// Drives the BH1745 INT pin, which switches the LEDs on breakout boards such as the Pimoroni one.
|
||||
class BH1745InterruptPinOutput : public output::BinaryOutput, public Parented<BH1745Component> {
|
||||
protected:
|
||||
void write_state(bool state) override { this->parent_->set_interrupt_state(state); }
|
||||
};
|
||||
|
||||
} // namespace esphome::bh1745
|
||||
@@ -26,7 +26,7 @@ class BLEClient;
|
||||
class BLEClientNode {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {};
|
||||
esp_ble_gattc_cb_param_t *param){};
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
|
||||
virtual void loop() {}
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
|
||||
@@ -104,19 +104,19 @@ struct HubCapabilities {
|
||||
// transition for every accepted or refused mode request - consumers skip
|
||||
// their own mode report on push builds.
|
||||
template<typename T>
|
||||
concept BLEHubContract =
|
||||
requires(T hub, ESPBTDeviceListener *listener, RawAdvertisementCallback raw_callback, uint8_t *mac) {
|
||||
hub.register_listener(listener);
|
||||
hub.set_raw_advertisement_callback(raw_callback);
|
||||
{ T::get_capabilities() } -> std::same_as<HubCapabilities>;
|
||||
hub.get_adapter_mac(mac);
|
||||
{ hub.scan_running() } -> std::same_as<bool>;
|
||||
{ hub.scan_active() } -> std::same_as<bool>;
|
||||
{ hub.request_scan_mode(true) } -> std::same_as<bool>;
|
||||
concept BLEHubContract = requires(T hub, ESPBTDeviceListener *listener, RawAdvertisementCallback raw_callback,
|
||||
uint8_t *mac) {
|
||||
hub.register_listener(listener);
|
||||
hub.set_raw_advertisement_callback(raw_callback);
|
||||
{ T::get_capabilities() } -> std::same_as<HubCapabilities>;
|
||||
hub.get_adapter_mac(mac);
|
||||
{ hub.scan_running() } -> std::same_as<bool>;
|
||||
{ hub.scan_active() } -> std::same_as<bool>;
|
||||
{ hub.request_scan_mode(true) } -> std::same_as<bool>;
|
||||
#ifdef USE_BLE_SCANNER_STATE_CALLBACK
|
||||
hub.set_scanner_state_callback(ScannerStateCallback{});
|
||||
{ hub.get_scanner_state() } -> std::same_as<ScannerState>;
|
||||
hub.set_scanner_state_callback(ScannerStateCallback{});
|
||||
{ hub.get_scanner_state() } -> std::same_as<ScannerState>;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace esphome::ble_device_base
|
||||
|
||||
@@ -284,9 +284,9 @@ bool BMI270Component::update_data(motion::MotionData &data) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
// Accelerometer: registers 0x0C–0x11 (6 bytes: x_lsb, x_msb, y_lsb, y_msb, z_lsb, z_msb)
|
||||
// When the magnetometer is active, the 8 aux data bytes (0x04–0x0B) sit directly before
|
||||
// the accel data, so one burst read from AUX_X_LSB fetches both.
|
||||
// Accelerometer: registers 0x0C–0x11 (6 bytes: x_lsb, x_msb, y_lsb, y_msb, z_lsb, z_msb)
|
||||
// When the magnetometer is active, the 8 aux data bytes (0x04–0x0B) sit directly before
|
||||
// the accel data, so one burst read from AUX_X_LSB fetches both.
|
||||
#ifdef USE_BMI270_BMM150
|
||||
const bool read_mag = this->magnetometer_ready_ && !this->magnetometer_callback_.empty();
|
||||
uint8_t buf[BMI270_AUX_DATA_LEN + REG_READ_LEN];
|
||||
|
||||
@@ -80,7 +80,8 @@ void BP1658CJ::loop() {
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
void BP1658CJ::write_channel(uint8_t channel, float state) {
|
||||
auto value = static_cast<uint16_t>(state * 0x3FF);
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::bp1658cj {
|
||||
|
||||
class BP1658CJ final : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
void set_max_power_color_channels(uint8_t max_power_color_channels) {
|
||||
@@ -29,23 +26,9 @@ class BP1658CJ final : public Component {
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel final : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP1658CJ *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
this->parent_->set_channel_value_(this->channel_, amount);
|
||||
}
|
||||
|
||||
BP1658CJ *parent_;
|
||||
uint8_t channel_;
|
||||
};
|
||||
void write_channel(uint8_t channel, float state);
|
||||
|
||||
protected:
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
@@ -8,21 +7,18 @@ from . import BP1658CJ
|
||||
|
||||
DEPENDENCIES = ["bp1658cj"]
|
||||
|
||||
Channel = BP1658CJ.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP1658CJ_ID = "bp1658cj_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP1658CJ_ID): cv.use_id(BP1658CJ),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=4),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_BP1658CJ_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
await output.sub_float_outputs(config, parent=config[CONF_BP1658CJ_ID]).create(
|
||||
config, None, config[CONF_CHANNEL], apply="write_channel(index, state)"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ class ByteBuffer {
|
||||
* Create a new Bytebuffer with the given capacity
|
||||
*/
|
||||
ByteBuffer(size_t capacity, Endian endianness = LITTLE)
|
||||
: data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity) {};
|
||||
: data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){};
|
||||
|
||||
// templated functions to implement putting and getting data of various types. There are two flavours of all
|
||||
// functions - one that uses the position as the offset, and updates the position accordingly, and one that
|
||||
|
||||
@@ -85,7 +85,7 @@ struct CanStatus {
|
||||
|
||||
class Canbus : public Component {
|
||||
public:
|
||||
Canbus() {};
|
||||
Canbus(){};
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
@@ -190,7 +190,7 @@ class CanbusTrigger final : public Trigger<std::vector<uint8_t>, uint32_t, bool>
|
||||
public:
|
||||
explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask,
|
||||
const bool use_extended_id)
|
||||
: parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id) {};
|
||||
: parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){};
|
||||
|
||||
void set_remote_transmission_request(bool remote_transmission_request) {
|
||||
this->remote_transmission_request_ = remote_transmission_request;
|
||||
|
||||
@@ -443,24 +443,6 @@ async def setup_climate_core_(var: MockObj, config: ConfigType) -> None:
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
|
||||
def set_custom_fan_modes(var: MockObj, modes: list[str]) -> None:
|
||||
"""Point the climate at a shared flash table of custom fan modes."""
|
||||
if modes:
|
||||
table = cg.shared_progmem_array(
|
||||
"climate_custom_fan_modes", cg.const_char_ptr, modes
|
||||
)
|
||||
cg.add(var.set_supported_custom_fan_modes_static(table, len(modes)))
|
||||
|
||||
|
||||
def set_custom_presets(var: MockObj, presets: list[str]) -> None:
|
||||
"""Point the climate at a shared flash table of custom presets."""
|
||||
if presets:
|
||||
table = cg.shared_progmem_array(
|
||||
"climate_custom_presets", cg.const_char_ptr, presets
|
||||
)
|
||||
cg.add(var.set_supported_custom_presets_static(table, len(presets)))
|
||||
|
||||
|
||||
async def register_climate(var: MockObj, config: ConfigType) -> None:
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
|
||||
@@ -479,8 +479,10 @@ void Climate::publish_state() {
|
||||
ClimateTraits Climate::get_traits() {
|
||||
auto traits = this->traits();
|
||||
// Wire custom mode pointers from Climate-owned storage
|
||||
traits.set_supported_custom_fan_modes_(&this->supported_custom_fan_modes_);
|
||||
traits.set_supported_custom_presets_(&this->supported_custom_presets_);
|
||||
if (this->supported_custom_fan_modes_)
|
||||
traits.set_supported_custom_fan_modes_(this->supported_custom_fan_modes_);
|
||||
if (this->supported_custom_presets_)
|
||||
traits.set_supported_custom_presets_(this->supported_custom_presets_);
|
||||
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
|
||||
if (!std::isnan(this->visual_min_temperature_override_)) {
|
||||
traits.set_visual_min_temperature(this->visual_min_temperature_override_);
|
||||
@@ -626,7 +628,7 @@ template<typename T> bool set_primary_mode(optional<T> &primary, const char *&cu
|
||||
* After: fan_mode=nullopt, custom_fan_mode_="Turbo" (pointer from traits)
|
||||
*
|
||||
* Lifetime Safety:
|
||||
* - found_ptr must come from Climate::find_custom_*_()
|
||||
* - found_ptr must come from traits.find_custom_*_mode_()
|
||||
* - Only pointers found in traits are stored, ensuring they remain valid
|
||||
* - Prevents dangling pointers from temporary strings
|
||||
*
|
||||
@@ -682,7 +684,11 @@ const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
|
||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
if (this->supported_custom_fan_modes_) {
|
||||
return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
}
|
||||
// Fallback for deprecated path: external components may set modes on ClimateTraits directly
|
||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||
@@ -690,7 +696,11 @@ const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
|
||||
return vector_find(this->supported_custom_presets_, custom_preset, len);
|
||||
if (this->supported_custom_presets_) {
|
||||
return vector_find(*this->supported_custom_presets_, custom_preset, len);
|
||||
}
|
||||
// Fallback for deprecated path: external components may set modes on ClimateTraits directly
|
||||
return this->get_traits().find_custom_preset_(custom_preset, len);
|
||||
}
|
||||
|
||||
void Climate::dump_traits_(const char *tag) {
|
||||
|
||||
@@ -243,34 +243,26 @@ class Climate : public EntityBase {
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Set the supported custom fan modes; the list is copied (stored on Climate, referenced by ClimateTraits).
|
||||
/// Set the supported custom fan modes (stored on Climate, referenced by ClimateTraits).
|
||||
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||
this->supported_custom_fan_modes_.assign_copy(modes.begin(), modes.size());
|
||||
this->ensure_custom_fan_modes_().assign(modes.begin(), modes.end());
|
||||
}
|
||||
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
||||
this->supported_custom_fan_modes_.assign_copy(modes.data(), modes.size());
|
||||
this->ensure_custom_fan_modes_() = modes;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
||||
this->supported_custom_fan_modes_.assign_copy(modes, N);
|
||||
}
|
||||
/// Points at a static table that outlives the climate (codegen); call before any runtime copy.
|
||||
void set_supported_custom_fan_modes_static(const char *const *modes, size_t count) {
|
||||
this->supported_custom_fan_modes_.assign_static(modes, count);
|
||||
this->ensure_custom_fan_modes_().assign(modes, modes + N);
|
||||
}
|
||||
|
||||
/// Set the supported custom presets; the list is copied (stored on Climate, referenced by ClimateTraits).
|
||||
/// Set the supported custom presets (stored on Climate, referenced by ClimateTraits).
|
||||
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
||||
this->supported_custom_presets_.assign_copy(presets.begin(), presets.size());
|
||||
this->ensure_custom_presets_().assign(presets.begin(), presets.end());
|
||||
}
|
||||
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
||||
this->supported_custom_presets_.assign_copy(presets.data(), presets.size());
|
||||
this->ensure_custom_presets_() = presets;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
||||
this->supported_custom_presets_.assign_copy(presets, N);
|
||||
}
|
||||
/// Points at a static table that outlives the climate (codegen); call before any runtime copy.
|
||||
void set_supported_custom_presets_static(const char *const *presets, size_t count) {
|
||||
this->supported_custom_presets_.assign_static(presets, count);
|
||||
this->ensure_custom_presets_().assign(presets, presets + N);
|
||||
}
|
||||
|
||||
/// Check if a custom fan mode is currently active.
|
||||
@@ -393,8 +385,22 @@ class Climate : public EntityBase {
|
||||
#endif
|
||||
|
||||
private:
|
||||
ClimateCustomModes supported_custom_fan_modes_;
|
||||
ClimateCustomModes supported_custom_presets_;
|
||||
/// Lazy-allocate custom mode vectors (never freed — entity lives forever).
|
||||
std::vector<const char *> &ensure_custom_fan_modes_() {
|
||||
if (!this->supported_custom_fan_modes_) {
|
||||
this->supported_custom_fan_modes_ = new std::vector<const char *>(); // NOLINT
|
||||
}
|
||||
return *this->supported_custom_fan_modes_;
|
||||
}
|
||||
std::vector<const char *> &ensure_custom_presets_() {
|
||||
if (!this->supported_custom_presets_) {
|
||||
this->supported_custom_presets_ = new std::vector<const char *>(); // NOLINT
|
||||
}
|
||||
return *this->supported_custom_presets_;
|
||||
}
|
||||
|
||||
std::vector<const char *> *supported_custom_fan_modes_{nullptr};
|
||||
std::vector<const char *> *supported_custom_presets_{nullptr};
|
||||
|
||||
/** The active custom fan mode (private - enforces use of safe setters).
|
||||
*
|
||||
|
||||
@@ -2,16 +2,31 @@
|
||||
|
||||
namespace esphome::climate {
|
||||
|
||||
ClimateTraits::ClimateTraits() = default;
|
||||
// Compat: shared empty vector for getters when no custom modes are set.
|
||||
// Remove in 2026.11.0 when deprecated ClimateTraits setters are removed
|
||||
// and getters can return const vector * instead of const vector &.
|
||||
static const std::vector<const char *> EMPTY_CUSTOM_MODES; // NOLINT
|
||||
|
||||
static const ClimateCustomModes EMPTY_CUSTOM_MODES; // NOLINT
|
||||
|
||||
const ClimateCustomModes &ClimateTraits::get_supported_custom_fan_modes() const {
|
||||
return this->supported_custom_fan_modes_ != nullptr ? *this->supported_custom_fan_modes_ : EMPTY_CUSTOM_MODES;
|
||||
const std::vector<const char *> &ClimateTraits::get_supported_custom_fan_modes() const {
|
||||
if (this->supported_custom_fan_modes_) {
|
||||
return *this->supported_custom_fan_modes_;
|
||||
}
|
||||
// Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0.
|
||||
if (!this->compat_custom_fan_modes_.empty()) {
|
||||
return this->compat_custom_fan_modes_;
|
||||
}
|
||||
return EMPTY_CUSTOM_MODES;
|
||||
}
|
||||
|
||||
const ClimateCustomModes &ClimateTraits::get_supported_custom_presets() const {
|
||||
return this->supported_custom_presets_ != nullptr ? *this->supported_custom_presets_ : EMPTY_CUSTOM_MODES;
|
||||
const std::vector<const char *> &ClimateTraits::get_supported_custom_presets() const {
|
||||
if (this->supported_custom_presets_) {
|
||||
return *this->supported_custom_presets_;
|
||||
}
|
||||
// Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0.
|
||||
if (!this->compat_custom_presets_.empty()) {
|
||||
return this->compat_custom_presets_;
|
||||
}
|
||||
return EMPTY_CUSTOM_MODES;
|
||||
}
|
||||
|
||||
int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "climate_mode.h"
|
||||
#include "esphome/core/finite_set_mask.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -18,12 +18,9 @@ using ClimateSwingModeMask =
|
||||
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
|
||||
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
|
||||
|
||||
/// Custom fan modes and custom presets: a flash table from codegen, or an owned copy set at runtime.
|
||||
using ClimateCustomModes = ConstVector<const char *, true>;
|
||||
|
||||
// Lightweight linear search for small lists (1-20 items) of const char* pointers
|
||||
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
||||
// Avoids std::find template overhead
|
||||
template<typename List> inline bool vector_contains(const List &vec, const char *value, size_t len) {
|
||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||
for (const char *item : vec) {
|
||||
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||
return true;
|
||||
@@ -31,12 +28,12 @@ template<typename List> inline bool vector_contains(const List &vec, const char
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename List> inline bool vector_contains(const List &vec, const char *value) {
|
||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
||||
return vector_contains(vec, value, strlen(value));
|
||||
}
|
||||
|
||||
// Find and return matching pointer from a list, or nullptr if not found
|
||||
template<typename List> inline const char *vector_find(const List &vec, const char *value, size_t len) {
|
||||
// Find and return matching pointer from vector, or nullptr if not found
|
||||
inline const char *vector_find(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||
for (const char *item : vec) {
|
||||
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||
return item;
|
||||
@@ -74,13 +71,9 @@ template<typename List> inline const char *vector_find(const List &vec, const ch
|
||||
class Climate; // Forward declaration
|
||||
|
||||
class ClimateTraits {
|
||||
friend class Climate; // Climate wires its custom mode lists into traits
|
||||
friend class Climate; // Allow Climate to access protected find methods
|
||||
|
||||
public:
|
||||
// Out of line on purpose: an inlined default init makes GCC copy the defaults from a .rodata
|
||||
// template per traits() override, and .rodata is RAM on ESP8266.
|
||||
ClimateTraits();
|
||||
|
||||
/// Get/set feature flags (see ClimateFeatures enum in climate_mode.h)
|
||||
uint32_t get_feature_flags() const { return this->feature_flags_; }
|
||||
void add_feature_flags(uint32_t feature_flags) { this->feature_flags_ |= feature_flags; }
|
||||
@@ -100,14 +93,42 @@ class ClimateTraits {
|
||||
if (!this->supported_fan_modes_.empty()) {
|
||||
return true;
|
||||
}
|
||||
return this->supported_custom_fan_modes_ != nullptr && !this->supported_custom_fan_modes_->empty();
|
||||
// Same precedence as get_supported_custom_fan_modes() getter
|
||||
if (this->supported_custom_fan_modes_) {
|
||||
return !this->supported_custom_fan_modes_->empty();
|
||||
}
|
||||
return !this->compat_custom_fan_modes_.empty(); // Compat: remove in 2026.11.0
|
||||
}
|
||||
const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||
|
||||
/// Empty when the climate has no custom fan modes. Set them on the Climate entity.
|
||||
const ClimateCustomModes &get_supported_custom_fan_modes() const;
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||
// Compat: store in owned vector. Copies copy the vector (deprecated path still copies this vector).
|
||||
this->compat_custom_fan_modes_ = modes;
|
||||
}
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
||||
this->compat_custom_fan_modes_ = modes;
|
||||
}
|
||||
// Remove before 2026.11.0
|
||||
template<size_t N>
|
||||
ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
||||
this->compat_custom_fan_modes_.assign(modes, modes + N);
|
||||
}
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_supported_custom_fan_modes(const std::vector<std::string> &modes) = delete;
|
||||
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) = delete;
|
||||
|
||||
// Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *.
|
||||
const std::vector<const char *> &get_supported_custom_fan_modes() const;
|
||||
bool supports_custom_fan_mode(const char *custom_fan_mode) const {
|
||||
return vector_contains(this->get_supported_custom_fan_modes(), custom_fan_mode);
|
||||
return (this->supported_custom_fan_modes_ &&
|
||||
vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode)) ||
|
||||
vector_contains(this->compat_custom_fan_modes_, custom_fan_mode); // Compat: remove in 2026.11.0
|
||||
}
|
||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||
return this->supports_custom_fan_mode(custom_fan_mode.c_str());
|
||||
@@ -119,10 +140,32 @@ class ClimateTraits {
|
||||
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
||||
const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
|
||||
|
||||
/// Empty when the climate has no custom presets. Set them on the Climate entity.
|
||||
const ClimateCustomModes &get_supported_custom_presets() const;
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
||||
this->compat_custom_presets_ = presets;
|
||||
}
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
||||
this->compat_custom_presets_ = presets;
|
||||
}
|
||||
// Remove before 2026.11.0
|
||||
template<size_t N>
|
||||
ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_custom_presets(const char *const (&presets)[N]) {
|
||||
this->compat_custom_presets_.assign(presets, presets + N);
|
||||
}
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_supported_custom_presets(const std::vector<std::string> &presets) = delete;
|
||||
void set_supported_custom_presets(std::initializer_list<std::string> presets) = delete;
|
||||
|
||||
// Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *.
|
||||
const std::vector<const char *> &get_supported_custom_presets() const;
|
||||
bool supports_custom_preset(const char *custom_preset) const {
|
||||
return vector_contains(this->get_supported_custom_presets(), custom_preset);
|
||||
return (this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset)) ||
|
||||
vector_contains(this->compat_custom_presets_, custom_preset); // Compat: remove in 2026.11.0
|
||||
}
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return this->supports_custom_preset(custom_preset.c_str());
|
||||
@@ -189,8 +232,38 @@ class ClimateTraits {
|
||||
}
|
||||
|
||||
/// Set custom mode pointers (only Climate::get_traits() should call these).
|
||||
void set_supported_custom_fan_modes_(const ClimateCustomModes *modes) { this->supported_custom_fan_modes_ = modes; }
|
||||
void set_supported_custom_presets_(const ClimateCustomModes *presets) { this->supported_custom_presets_ = presets; }
|
||||
void set_supported_custom_fan_modes_(const std::vector<const char *> *modes) {
|
||||
this->supported_custom_fan_modes_ = modes;
|
||||
}
|
||||
void set_supported_custom_presets_(const std::vector<const char *> *presets) {
|
||||
this->supported_custom_presets_ = presets;
|
||||
}
|
||||
|
||||
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
|
||||
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
|
||||
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||
}
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const {
|
||||
if (this->supported_custom_fan_modes_) {
|
||||
return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
}
|
||||
// Compat: check owned vector from deprecated setters. Remove in 2026.11.0.
|
||||
return vector_find(this->compat_custom_fan_modes_, custom_fan_mode, len);
|
||||
}
|
||||
|
||||
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
||||
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
|
||||
const char *find_custom_preset_(const char *custom_preset) const {
|
||||
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||
}
|
||||
const char *find_custom_preset_(const char *custom_preset, size_t len) const {
|
||||
if (this->supported_custom_presets_) {
|
||||
return vector_find(*this->supported_custom_presets_, custom_preset, len);
|
||||
}
|
||||
// Compat: check owned vector from deprecated setters. Remove in 2026.11.0.
|
||||
return vector_find(this->compat_custom_presets_, custom_preset, len);
|
||||
}
|
||||
|
||||
uint32_t feature_flags_{0};
|
||||
float visual_min_temperature_{10};
|
||||
@@ -206,10 +279,17 @@ class ClimateTraits {
|
||||
climate::ClimatePresetMask supported_presets_;
|
||||
TemperatureUnit temperature_unit_{TemperatureUnit::CELSIUS};
|
||||
|
||||
/// Owned by the Climate entity; get_traits() wires these pointers.
|
||||
const ClimateCustomModes *supported_custom_fan_modes_{nullptr};
|
||||
const ClimateCustomModes *supported_custom_presets_{nullptr};
|
||||
/** Custom mode storage - pointers to vectors owned by the Climate base class.
|
||||
*
|
||||
* ClimateTraits does not own this data; Climate stores the vectors and
|
||||
* get_traits() wires these pointers automatically.
|
||||
*/
|
||||
const std::vector<const char *> *supported_custom_fan_modes_{nullptr};
|
||||
const std::vector<const char *> *supported_custom_presets_{nullptr};
|
||||
// Compat: owned storage for deprecated setters. Copies copy the vector (copies include this vector).
|
||||
// Remove in 2026.11.0.
|
||||
std::vector<const char *> compat_custom_fan_modes_;
|
||||
std::vector<const char *> compat_custom_presets_;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<ClimateTraits>, "ClimateTraits is copied on every traits() call");
|
||||
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -15,7 +15,6 @@ CONF_ANNOUNCEMENT_VOLUME = "announcement_volume"
|
||||
CONF_AQI = "aqi"
|
||||
CONF_AUTOMATIC_BASELINE_CALIBRATION = "automatic_baseline_calibration"
|
||||
CONF_B_CONSTANT = "b_constant"
|
||||
CONF_BITRATE = "bitrate"
|
||||
CONF_BLUE_CHANNEL = "blue_channel"
|
||||
CONF_BORDER_ROUTER = "border_router"
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
|
||||
@@ -48,15 +48,14 @@ void DAC7678Output::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
void DAC7678Output::register_channel(DAC7678Channel *channel) {
|
||||
auto c = channel->channel_;
|
||||
this->min_channel_ = std::min(this->min_channel_, c);
|
||||
this->max_channel_ = std::max(this->max_channel_, c);
|
||||
channel->set_parent(this);
|
||||
ESP_LOGV(TAG, "Registered channel: %01u", channel->channel_);
|
||||
void DAC7678Output::register_channel(uint8_t channel) {
|
||||
this->min_channel_ = std::min(this->min_channel_, channel);
|
||||
this->max_channel_ = std::max(this->max_channel_, channel);
|
||||
ESP_LOGV(TAG, "Registered channel: %01u", channel);
|
||||
}
|
||||
|
||||
void DAC7678Output::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
void DAC7678Output::write_channel(uint8_t channel, float state) {
|
||||
auto value = static_cast<uint16_t>(roundf(state * 0xFFF));
|
||||
if (channel >= std::size(this->dac_input_reg_))
|
||||
return;
|
||||
if (this->dac_input_reg_[channel] != value) {
|
||||
@@ -71,10 +70,4 @@ void DAC7678Output::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void DAC7678Channel::write_state(float state) {
|
||||
const float input_rounded = roundf(state * this->full_scale_);
|
||||
auto input = static_cast<uint16_t>(input_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, input);
|
||||
}
|
||||
|
||||
} // namespace esphome::dac7678
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::dac7678 {
|
||||
|
||||
class DAC7678Output;
|
||||
|
||||
class DAC7678Channel final : public output::FloatOutput, public Parented<DAC7678Output> {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
friend class DAC7678Output;
|
||||
|
||||
const uint16_t full_scale_ = 0xFFF;
|
||||
|
||||
void write_state(float state) override;
|
||||
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
/// DAC7678 float output component.
|
||||
class DAC7678Output final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
DAC7678Output() {}
|
||||
|
||||
void register_channel(DAC7678Channel *channel);
|
||||
void register_channel(uint8_t channel);
|
||||
void write_channel(uint8_t channel, float state);
|
||||
|
||||
void set_internal_reference(const bool value) { this->internal_reference_ = value; }
|
||||
|
||||
@@ -37,12 +20,8 @@ class DAC7678Output final : public Component, public i2c::I2CDevice {
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
friend DAC7678Channel;
|
||||
|
||||
bool internal_reference_;
|
||||
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
|
||||
uint8_t min_channel_{0xFF};
|
||||
uint8_t max_channel_{0x00};
|
||||
uint16_t dac_input_reg_[8] = {
|
||||
|
||||
@@ -2,29 +2,26 @@ import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import DAC7678Output, dac7678_ns
|
||||
from . import DAC7678Output
|
||||
|
||||
DEPENDENCIES = ["dac7678"]
|
||||
|
||||
DAC7678Channel = dac7678_ns.class_("DAC7678Channel", output.FloatOutput)
|
||||
CONF_DAC7678_ID = "dac7678_id"
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(DAC7678Channel),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
cv.GenerateID(CONF_DAC7678_ID): cv.use_id(DAC7678Output),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> MockObj:
|
||||
paren = await cg.get_variable(config[CONF_DAC7678_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(paren.register_channel(var))
|
||||
await output.register_output(var, config)
|
||||
return var
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_DAC7678_ID])
|
||||
await output.sub_float_outputs(config, parent=hub).create(
|
||||
config, None, config[CONF_CHANNEL], apply="write_channel(index, state)"
|
||||
)
|
||||
cg.add(hub.register_channel(config[CONF_CHANNEL]))
|
||||
|
||||
@@ -96,9 +96,9 @@ static void __attribute__((flatten)) draw_packed_pixels(Display *display, int x_
|
||||
|
||||
// Remove before 2027.5.0. Draws a block in the deprecated BGR or GRB order, pixel by pixel. Not inlined, so
|
||||
// the RGB loops above compile as tightly as before.
|
||||
static void __attribute__((noinline)) draw_ordered_pixels(Display *display, int x_start, int y_start, int w, int h,
|
||||
const uint8_t *ptr, ColorOrder order, ColorBitness bitness,
|
||||
bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
static void __attribute__((noinline))
|
||||
draw_ordered_pixels(Display *display, int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
const size_t line_stride = x_offset + w + x_pad;
|
||||
for (int y = 0; y != h; y++) {
|
||||
size_t source_idx = (y_offset + y) * line_stride + x_offset;
|
||||
|
||||
@@ -198,8 +198,7 @@ template<typename T> class DisplayWriter {
|
||||
|
||||
// For stateless lambdas (convertible to function pointer): use function pointer (4 bytes)
|
||||
template<typename F>
|
||||
DisplayWriter(F f)
|
||||
requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
|
||||
DisplayWriter(F f) requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
|
||||
: type_(STATELESS_LAMBDA) {
|
||||
this->stateless_f_ = f; // Implicit conversion to function pointer
|
||||
}
|
||||
@@ -207,9 +206,7 @@ template<typename T> class DisplayWriter {
|
||||
// For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated)
|
||||
// This handles backwards compatibility with external components
|
||||
template<typename F>
|
||||
DisplayWriter(F f)
|
||||
requires std::invocable<F, T &> && (!std::convertible_to<F, void (*)(T &)>)
|
||||
: type_(LAMBDA) {
|
||||
DisplayWriter(F f) requires std::invocable<F, T &> &&(!std::convertible_to<F, void (*)(T &)>) : type_(LAMBDA) {
|
||||
this->f_ = new std::function<void(T &)>(std::move(f));
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,10 @@ class DisplayMenuComponent : public Component {
|
||||
update();
|
||||
}
|
||||
|
||||
virtual void on_before_show() {};
|
||||
virtual void on_after_show() {};
|
||||
virtual void on_before_hide() {};
|
||||
virtual void on_after_hide() {};
|
||||
virtual void on_before_show(){};
|
||||
virtual void on_after_show(){};
|
||||
virtual void on_before_hide(){};
|
||||
virtual void on_after_hide(){};
|
||||
|
||||
uint8_t rows_;
|
||||
bool active_;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network, socket
|
||||
from esphome.components import socket
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.components.light.types import AddressableLightEffect
|
||||
import esphome.config_validation as cv
|
||||
@@ -38,7 +38,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
network.require_ipv4("e131"),
|
||||
socket.consume_sockets(1, "e131", socket.SocketType.UDP),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_EMC2101_ID, EMC2101_COMPONENT_SCHEMA, emc2101_ns
|
||||
from .. import CONF_EMC2101_ID, EMC2101_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["emc2101"]
|
||||
|
||||
EMC2101Output = emc2101_ns.class_("EMC2101Output", output.FloatOutput)
|
||||
|
||||
CONFIG_SCHEMA = EMC2101_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(EMC2101Output),
|
||||
cv.Required(CONF_ID): cv.declare_id(output.ApplyFloatOutput),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
paren = await cg.get_variable(config[CONF_EMC2101_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
await output.register_output(var, config)
|
||||
await output.sub_float_outputs(config, parent=config[CONF_EMC2101_ID]).create(
|
||||
config, None, apply="set_duty_cycle(state)"
|
||||
)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "emc2101_output.h"
|
||||
|
||||
namespace esphome::emc2101 {
|
||||
|
||||
void EMC2101Output::write_state(float state) { this->parent_->set_duty_cycle(state); }
|
||||
|
||||
} // namespace esphome::emc2101
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../emc2101.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome::emc2101 {
|
||||
|
||||
/// This class allows to control the EMC2101 output.
|
||||
class EMC2101Output final : public output::FloatOutput {
|
||||
public:
|
||||
EMC2101Output(Emc2101Component *parent) : parent_(parent) {}
|
||||
|
||||
protected:
|
||||
/** Used by ESPHome framework. */
|
||||
void write_state(float state) override;
|
||||
|
||||
Emc2101Component *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::emc2101
|
||||
@@ -22,11 +22,11 @@ void EPaperMono::deep_sleep() {
|
||||
}
|
||||
|
||||
bool EPaperMono::reset() {
|
||||
if (!EPaperBase::reset())
|
||||
return false;
|
||||
if (this->software_reset_)
|
||||
if (EPaperBase::reset()) {
|
||||
this->command(0x12);
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EPaperMono::set_window() {
|
||||
|
||||
@@ -15,13 +15,12 @@ class EPaperMono : public EPaperBase {
|
||||
protected:
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override {}
|
||||
void power_off() override {};
|
||||
void power_off() override{};
|
||||
void deep_sleep() override;
|
||||
bool reset() override;
|
||||
virtual void set_window();
|
||||
bool transfer_data() override;
|
||||
bool send_red_{true};
|
||||
bool software_reset_{true}; // send 0x12 after the hardware reset
|
||||
// When set, the red RAM pass writes the image rather than blank data.
|
||||
bool send_red_as_image_{false};
|
||||
};
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "epaper_spi_ssd1681.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
ESPHOME_LOG_TAG(TAG, "epaper_spi.ssd1681");
|
||||
|
||||
bool HOT EPaperSSD1681::transfer_data() {
|
||||
if (this->current_data_index_ == 0 && this->send_red_) {
|
||||
if (!this->partial_) {
|
||||
// A refresh drives the whole panel from RAM, so a full one makes both RAM banks whole again
|
||||
this->x_low_ = 0;
|
||||
this->x_high_ = this->width_;
|
||||
this->y_low_ = 0;
|
||||
this->y_high_ = this->height_;
|
||||
} else {
|
||||
// 0x26 already holds the image on the panel: the controller copies 0x24 into it after each refresh
|
||||
this->set_window();
|
||||
this->send_red_ = false;
|
||||
}
|
||||
}
|
||||
return EPaperSSD1683::transfer_data();
|
||||
}
|
||||
|
||||
void EPaperSSD1681::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh screen");
|
||||
this->cmd_data(0x3C, {partial ? (uint8_t) 0x80 : (uint8_t) 0x01});
|
||||
// 0x26 is compared as it is on a partial refresh and ignored on a full one
|
||||
this->cmd_data(0x21, {partial ? (uint8_t) 0x00 : (uint8_t) 0x40, (uint8_t) 0x00});
|
||||
// Both end with analog and OSC off before the panel goes to sleep
|
||||
this->cmd_data(0x22, {partial ? (uint8_t) 0xFF : (uint8_t) 0xF7});
|
||||
this->command(0x20);
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi_ssd1683.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
// Partial refreshes compare RAM 0x24 (new image) with RAM 0x26 (image on the panel) and drive only changed pixels.
|
||||
class EPaperSSD1681 : public EPaperSSD1683 {
|
||||
public:
|
||||
EPaperSSD1681(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperSSD1683(name, width, height, init_sequence, init_sequence_length) {
|
||||
// 0x12 puts the display option register back to its default, which turns off the RAM ping-pong the panel's
|
||||
// OTP enables; without it the controller no longer carries 0x24 over to 0x26 after a partial refresh
|
||||
this->software_reset_ = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool transfer_data() override;
|
||||
void refresh_screen(bool partial) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -1,42 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DIMENSIONS, CONF_RESET_PIN
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .ssd1683 import SSD1683
|
||||
|
||||
MAX_WIDTH = 200
|
||||
MAX_HEIGHT = 200
|
||||
|
||||
|
||||
class SSD1681(SSD1683):
|
||||
source_files = (
|
||||
"epaper_spi_mono.cpp",
|
||||
"epaper_spi_ssd1681.cpp",
|
||||
"epaper_spi_ssd1683.cpp",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, name: str, class_name: str = "EPaperSSD1681", **defaults: Any
|
||||
) -> None:
|
||||
super().__init__(name, class_name=class_name, **defaults)
|
||||
|
||||
def validate_config(self, config: ConfigType) -> ConfigType:
|
||||
config = super().validate_config(config)
|
||||
width, height = self.get_dimensions(config)
|
||||
if width > MAX_WIDTH or height > MAX_HEIGHT:
|
||||
raise cv.Invalid(
|
||||
f"{self.name} supports at most {MAX_WIDTH}x{MAX_HEIGHT} pixels",
|
||||
path=[CONF_DIMENSIONS],
|
||||
)
|
||||
if CONF_RESET_PIN not in config:
|
||||
# The panel sleeps after every update, and only the hardware reset wakes it
|
||||
raise cv.Invalid(f"{self.name} needs a {CONF_RESET_PIN}")
|
||||
return config
|
||||
|
||||
|
||||
ssd1681 = SSD1681("ssd1681", width=200, height=200)
|
||||
|
||||
ssd1681.extend("goodisplay-gdey0154d67-1.54")
|
||||
ssd1681.extend("waveshare-1.54in-v2")
|
||||
@@ -31,3 +31,15 @@ goodisplay_gdey042t81 = ssd1683.extend(
|
||||
width=400,
|
||||
height=300,
|
||||
)
|
||||
|
||||
# The SSD1681 shares the SSD1683 command set for everything this driver uses:
|
||||
# the same 0x01/0x11/0x18 initialisation, the same 0x44/0x45 window and
|
||||
# 0x4E/0x4F cursor registers with a byte-addressed X and a 16-bit Y, and the
|
||||
# same 0x22/0x20 update. It only differs in the panel sizes it drives.
|
||||
ssd1681 = SSD1683("ssd1681")
|
||||
|
||||
waveshare_1_54in_v2 = ssd1681.extend(
|
||||
"waveshare-1.54in-v2",
|
||||
width=200,
|
||||
height=200,
|
||||
)
|
||||
|
||||
@@ -74,7 +74,6 @@ from .const import (
|
||||
KEY_EXTRA_BUILD_FILES,
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_FULL_CERT_BUNDLE,
|
||||
KEY_IDF_EXTRA_TARGETS,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_MBEDTLS_SDKCONFIG,
|
||||
KEY_NETWORK_SDKCONFIG,
|
||||
@@ -82,7 +81,6 @@ from .const import (
|
||||
KEY_REF,
|
||||
KEY_REPO,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_SRC_CMAKE,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
@@ -705,21 +703,10 @@ def set_core_data(config):
|
||||
CORE.data[KEY_ESP32][KEY_FLASH_SIZE] = config[CONF_FLASH_SIZE]
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
||||
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
|
||||
CORE.data[KEY_ESP32][KEY_SRC_CMAKE] = []
|
||||
CORE.data[KEY_ESP32][KEY_IDF_EXTRA_TARGETS] = set()
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _require_ipv4_on_arduino(config: ConfigType) -> ConfigType:
|
||||
"""The Arduino core's IPAddress.cpp does not compile with IPv4 disabled."""
|
||||
if CORE.using_arduino:
|
||||
from esphome.components import network
|
||||
|
||||
network.require_ipv4(PLATFORM_ESP32)(config)
|
||||
return config
|
||||
|
||||
|
||||
def get_esp32_variant(core_obj=None):
|
||||
return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT]
|
||||
|
||||
@@ -1103,26 +1090,6 @@ def add_extra_script(stage: str, filename: str, path: Path):
|
||||
cg.add_platformio_option("extra_scripts", [key])
|
||||
|
||||
|
||||
def add_idf_install_target(target: str) -> None:
|
||||
"""Install the ESP-IDF tools for another chip alongside the variant's own.
|
||||
|
||||
The native ESP-IDF toolchain install is limited to the variant being
|
||||
built. A component that needs another architecture's compiler names that
|
||||
chip here; ``target`` is an IDF chip name such as ``esp32c3``.
|
||||
"""
|
||||
CORE.data[KEY_ESP32][KEY_IDF_EXTRA_TARGETS].add(target)
|
||||
|
||||
|
||||
def add_src_cmake(block: str) -> None:
|
||||
"""Append CMake to the generated src/CMakeLists.txt.
|
||||
|
||||
The block is emitted after ``idf_component_register`` so it can use
|
||||
``COMPONENT_LIB`` and friends; IDF's requirements pass stops at
|
||||
``idf_component_register`` so the block never runs in script mode.
|
||||
"""
|
||||
CORE.data[KEY_ESP32][KEY_SRC_CMAKE].append(block)
|
||||
|
||||
|
||||
def add_extra_build_file(filename: str, path: Path) -> bool:
|
||||
"""Add an extra build file to the project."""
|
||||
if filename not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
@@ -2503,7 +2470,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
_set_default_framework,
|
||||
_check_versions,
|
||||
set_core_data,
|
||||
_require_ipv4_on_arduino,
|
||||
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,13 +3,7 @@ import esphome.codegen as cg
|
||||
# Re-exported for the many esp32-side users; defined in esphome.const
|
||||
# and esphome.espidf so the upload/logs fast path can use them without
|
||||
# importing this package.
|
||||
from esphome.const import ( # noqa: F401 # pylint: disable=unused-import
|
||||
KEY_ESP32,
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_IDF_EXTRA_TARGETS,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_VARIANT,
|
||||
)
|
||||
from esphome.const import KEY_ESP32, KEY_FLASH_SIZE, KEY_IDF_VERSION, KEY_VARIANT # noqa: F401 # pylint: disable=unused-import
|
||||
|
||||
# Back compat for external components only; in-tree callers import it
|
||||
# from esphome.espidf directly.
|
||||
@@ -26,7 +20,6 @@ KEY_REFRESH = "refresh"
|
||||
KEY_PATH = "path"
|
||||
KEY_SUBMODULES = "submodules"
|
||||
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
||||
KEY_SRC_CMAKE = "src_cmake"
|
||||
KEY_CERT_BUNDLE = "cert_bundle"
|
||||
KEY_FULL_CERT_BUNDLE = "full_cert_bundle"
|
||||
KEY_NETWORK_SDKCONFIG = "network_sdkconfig"
|
||||
|
||||
@@ -153,8 +153,8 @@ struct RawCrashData {
|
||||
uint32_t other_backtrace[MAX_BACKTRACE];
|
||||
#endif
|
||||
};
|
||||
static RawCrashData
|
||||
__attribute__((section(".noinit"))) s_raw_crash_data; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static RawCrashData __attribute__((section(".noinit")))
|
||||
s_raw_crash_data; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// Whether crash data was found and validated this boot.
|
||||
static bool s_crash_data_valid = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -238,7 +238,7 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
|
||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
gpio::Flags diff = (gpio::Flags) (flags ^ arg->flags);
|
||||
gpio::Flags diff = (gpio::Flags)(flags ^ arg->flags);
|
||||
if (diff & gpio::FLAG_OUTPUT) {
|
||||
if (flags & gpio::FLAG_OUTPUT) {
|
||||
gpio_hal_output_enable(&GPIO_HAL, arg->pin);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// WORKAROUND, because CAN_IO_UNUSED is just defined as (-1) in this version
|
||||
// of the framework which does not work with -fpermissive
|
||||
#undef CAN_IO_UNUSED
|
||||
#define CAN_IO_UNUSED (static_cast<gpio_num_t>(-1))
|
||||
#define CAN_IO_UNUSED ((gpio_num_t) -1)
|
||||
|
||||
namespace esphome::esp32_can {
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class ESP32Can final : public canbus::Canbus {
|
||||
this->tx_enqueue_timeout_ticks_ = pdMS_TO_TICKS(tx_enqueue_timeout_ms);
|
||||
}
|
||||
canbus::CanStatus get_status() override;
|
||||
ESP32Can() {};
|
||||
ESP32Can(){};
|
||||
|
||||
protected:
|
||||
bool setup_internal() override;
|
||||
|
||||
@@ -19,10 +19,10 @@ void ESP32PowerManagement::setup() {
|
||||
this->min_freq_mhz_ = this->max_freq_mhz_;
|
||||
|
||||
esp_pm_config_t pm_config = {
|
||||
.max_freq_mhz = this->max_freq_mhz_,
|
||||
.min_freq_mhz = this->min_freq_mhz_,
|
||||
.max_freq_mhz = this->max_freq_mhz_,
|
||||
.min_freq_mhz = this->min_freq_mhz_,
|
||||
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
|
||||
.light_sleep_enable = true,
|
||||
.light_sleep_enable = true,
|
||||
#endif
|
||||
};
|
||||
esp_err_t rc = esp_pm_configure(&pm_config);
|
||||
|
||||
@@ -261,11 +261,16 @@ void ESP32TouchComponent::dump_config() {
|
||||
if (this->filter_configured_) {
|
||||
// TOUCH_BM_IIR_FILTER_256 only exists on V2, shifting JITTER's position
|
||||
static constexpr const char *FILTER_STRS[] = {
|
||||
"IIR_4", "IIR_8", "IIR_16", "IIR_32", "IIR_64", "IIR_128",
|
||||
"IIR_4",
|
||||
"IIR_8",
|
||||
"IIR_16",
|
||||
"IIR_32",
|
||||
"IIR_64",
|
||||
"IIR_128",
|
||||
#if SOC_TOUCH_SENSOR_VERSION == 2
|
||||
"IIR_256",
|
||||
"IIR_256",
|
||||
#endif
|
||||
"JITTER",
|
||||
"JITTER",
|
||||
};
|
||||
static constexpr const char *SMOOTH_STRS[] = {"OFF", "IIR_2", "IIR_4", "IIR_8"};
|
||||
const char *filter_s = lookup_str(FILTER_STRS, this->filter_mode_);
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
"""Run a user program on the ESP32 ULP coprocessor.
|
||||
|
||||
The coprocessor is picked from the variant: the ULP RISC-V core on the
|
||||
ESP32-S2/S3 and the LP core on the ESP32-C5/C6/P4.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32
|
||||
from esphome.components.const import CONF_SOURCES
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
add_extra_build_file,
|
||||
add_idf_install_target,
|
||||
add_idf_sdkconfig_option,
|
||||
add_src_cmake,
|
||||
get_esp32_variant,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
DOMAIN = "esp32_ulp"
|
||||
|
||||
CONF_WAKEUP_PERIOD = "wakeup_period"
|
||||
CONF_WAKEUP_SOURCES = "wakeup_sources"
|
||||
CONF_RESERVE_MEMORY = "reserve_memory"
|
||||
CONF_RUN_ON_BOOT = "run_on_boot"
|
||||
CONF_WAKEUP_FROM_SLEEP = "wakeup_from_sleep"
|
||||
|
||||
BACKEND_RISCV = "riscv"
|
||||
BACKEND_LP_CORE = "lp_core"
|
||||
|
||||
# Which coprocessor each variant has (sdkconfig ULP_COPROC_TYPE choice)
|
||||
VARIANT_BACKEND = {
|
||||
VARIANT_ESP32S2: BACKEND_RISCV,
|
||||
VARIANT_ESP32S3: BACKEND_RISCV,
|
||||
VARIANT_ESP32C5: BACKEND_LP_CORE,
|
||||
VARIANT_ESP32C6: BACKEND_LP_CORE,
|
||||
VARIANT_ESP32P4: BACKEND_LP_CORE,
|
||||
}
|
||||
# Upper bound of ULP_COPROC_RESERVE_MEM per variant (components/ulp/Kconfig)
|
||||
RESERVE_MEMORY_MAX = {
|
||||
VARIANT_ESP32S2: 8176,
|
||||
VARIANT_ESP32S3: 8176,
|
||||
VARIANT_ESP32C5: 16352,
|
||||
VARIANT_ESP32C6: 16352,
|
||||
VARIANT_ESP32P4: 31088,
|
||||
}
|
||||
# LP core wake sources (ULP_LP_CORE_WAKEUP_SOURCE_*); the LP timer is
|
||||
# implied by wakeup_period.
|
||||
WAKEUP_SOURCES = {
|
||||
"hp_cpu": "HP_CPU",
|
||||
"lp_io": "LP_IO",
|
||||
"lp_uart": "LP_UART",
|
||||
"etm": "ETM",
|
||||
}
|
||||
# The S2/S3 ULP RISC-V build needs the RISC-V compiler, which the native
|
||||
# toolchain install skips for Xtensa variants; any RISC-V chip pulls it in.
|
||||
RISCV_TOOLCHAIN_TARGET = "esp32c3"
|
||||
# The ULP RISC-V linker puts the stack top at the end of the reservation as
|
||||
# is, and the RISC-V ABI wants the stack pointer 16-byte aligned
|
||||
RISCV_STACK_ALIGNMENT = 16
|
||||
# Both backends store the period in a uint32_t of microseconds
|
||||
WAKEUP_PERIOD_MAX_US = 2**32 - 1
|
||||
|
||||
ULP_SOURCE_SUFFIXES = (".c", ".S", ".s")
|
||||
ULP_HEADER_SUFFIXES = (".h",)
|
||||
ULP_BUILD_SUBDIR = "ulp"
|
||||
ULP_APP_NAME = "ulp_main"
|
||||
# CMake appended to the generated src/CMakeLists.txt; IDF's ulp_embed_binary builds
|
||||
# the program, embeds it and generates ulp_main.h for the listed dependents.
|
||||
ULP_CMAKE = """
|
||||
# ULP program (esp32_ulp)
|
||||
ulp_embed_binary({app} "{sources}" "{dependents}")"""
|
||||
# Sources that include the generated header must be compiled after the ULP
|
||||
# program is built: main.cpp for lambdas and this component.
|
||||
ULP_HEADER_DEPENDENTS = ("main.cpp", "esphome/components/esp32_ulp/esp32_ulp.cpp")
|
||||
|
||||
esp32_ulp_ns = cg.esphome_ns.namespace("esp32_ulp")
|
||||
ESP32ULP = esp32_ulp_ns.class_("ESP32ULP", cg.Component)
|
||||
|
||||
|
||||
def get_backend() -> str:
|
||||
return VARIANT_BACKEND[get_esp32_variant()]
|
||||
|
||||
|
||||
def _validate_source(value: object) -> Path:
|
||||
path = CORE.relative_config_path(cv.file_(value))
|
||||
if path.suffix not in ULP_SOURCE_SUFFIXES + ULP_HEADER_SUFFIXES:
|
||||
raise cv.Invalid(
|
||||
f"ULP sources must be C, assembly or header files ({', '.join(ULP_SOURCE_SUFFIXES + ULP_HEADER_SUFFIXES)})"
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def _validate_sources(value: list[Path]) -> list[Path]:
|
||||
names = [path.name for path in value]
|
||||
duplicates = {name for name in names if names.count(name) > 1}
|
||||
if duplicates:
|
||||
raise cv.Invalid(
|
||||
f"ULP source file names must be unique, duplicated: {', '.join(sorted(duplicates))}"
|
||||
)
|
||||
if not any(path.suffix in ULP_SOURCE_SUFFIXES for path in value):
|
||||
raise cv.Invalid("At least one ULP source must be a C or assembly file")
|
||||
return value
|
||||
|
||||
|
||||
def _validate_variant(config: ConfigType) -> ConfigType:
|
||||
variant = get_esp32_variant()
|
||||
backend = VARIANT_BACKEND[variant]
|
||||
reserve = config[CONF_RESERVE_MEMORY]
|
||||
if reserve > RESERVE_MEMORY_MAX[variant]:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_RESERVE_MEMORY} may be at most {RESERVE_MEMORY_MAX[variant]} bytes on {variant}",
|
||||
path=[CONF_RESERVE_MEMORY],
|
||||
)
|
||||
if backend == BACKEND_RISCV:
|
||||
if reserve % RISCV_STACK_ALIGNMENT:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_RESERVE_MEMORY} must be a multiple of {RISCV_STACK_ALIGNMENT} bytes on {variant}",
|
||||
path=[CONF_RESERVE_MEMORY],
|
||||
)
|
||||
if CONF_WAKEUP_SOURCES in config:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_WAKEUP_SOURCES} is only available on the LP core ({', '.join(v for v, b in VARIANT_BACKEND.items() if b == BACKEND_LP_CORE)})",
|
||||
path=[CONF_WAKEUP_SOURCES],
|
||||
)
|
||||
if CONF_WAKEUP_PERIOD not in config:
|
||||
raise cv.Invalid(f"{CONF_WAKEUP_PERIOD} is required")
|
||||
elif CONF_WAKEUP_PERIOD not in config and not config.get(CONF_WAKEUP_SOURCES):
|
||||
raise cv.Invalid(
|
||||
f"The LP core needs {CONF_WAKEUP_PERIOD} or {CONF_WAKEUP_SOURCES} to know when to run"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32ULP),
|
||||
cv.Required(CONF_SOURCES): cv.All(
|
||||
cv.ensure_list(_validate_source),
|
||||
cv.Length(min=1),
|
||||
_validate_sources,
|
||||
),
|
||||
cv.Optional(CONF_WAKEUP_PERIOD): cv.All(
|
||||
cv.positive_time_period_microseconds,
|
||||
cv.Range(
|
||||
min=cv.TimePeriod(microseconds=1),
|
||||
max=cv.TimePeriod(microseconds=WAKEUP_PERIOD_MAX_US),
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_WAKEUP_SOURCES): cv.ensure_list(
|
||||
cv.one_of(*WAKEUP_SOURCES, lower=True)
|
||||
),
|
||||
cv.Optional(CONF_RESERVE_MEMORY, default=4096): cv.int_range(
|
||||
min=32, max=max(RESERVE_MEMORY_MAX.values())
|
||||
),
|
||||
cv.Optional(CONF_RUN_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_WAKEUP_FROM_SLEEP, default=True): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
esp32.only_on_variant(
|
||||
supported=list(VARIANT_BACKEND),
|
||||
msg_prefix="The ULP coprocessor",
|
||||
),
|
||||
_validate_variant,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
if not CORE.using_toolchain_esp_idf:
|
||||
raise cv.Invalid("esp32_ulp requires the native ESP-IDF build")
|
||||
if get_backend() == BACKEND_RISCV:
|
||||
add_idf_install_target(RISCV_TOOLCHAIN_TARGET)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
backend = get_backend()
|
||||
|
||||
# Headers are copied next to the sources so the program can include them,
|
||||
# but only C and assembly files go to the ULP build.
|
||||
names: list[str] = []
|
||||
for path in config[CONF_SOURCES]:
|
||||
add_extra_build_file(f"{ULP_BUILD_SUBDIR}/{path.name}", path)
|
||||
if path.suffix in ULP_SOURCE_SUFFIXES:
|
||||
names.append(path.name)
|
||||
|
||||
add_idf_sdkconfig_option("CONFIG_ULP_COPROC_ENABLED", True)
|
||||
if backend == BACKEND_RISCV:
|
||||
add_idf_sdkconfig_option("CONFIG_ULP_COPROC_TYPE_RISCV", True)
|
||||
else:
|
||||
add_idf_sdkconfig_option("CONFIG_ULP_COPROC_TYPE_LP_CORE", True)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ULP_COPROC_RESERVE_MEM", config[CONF_RESERVE_MEMORY]
|
||||
)
|
||||
include_builtin_idf_component("ulp")
|
||||
|
||||
sources = ";".join(
|
||||
f"${{CMAKE_CURRENT_SOURCE_DIR}}/../{ULP_BUILD_SUBDIR}/{name}" for name in names
|
||||
)
|
||||
dependents = ";".join(
|
||||
f"${{CMAKE_CURRENT_SOURCE_DIR}}/{src}" for src in ULP_HEADER_DEPENDENTS
|
||||
)
|
||||
add_src_cmake(
|
||||
ULP_CMAKE.format(app=ULP_APP_NAME, sources=sources, dependents=dependents)
|
||||
)
|
||||
# Expose the program's global variables (ulp_<name>) to lambdas.
|
||||
cg.add_global(cg.RawStatement(f'#include "{ULP_APP_NAME}.h"'))
|
||||
|
||||
if (period := config.get(CONF_WAKEUP_PERIOD)) is not None:
|
||||
cg.add(var.set_wakeup_period(period))
|
||||
if backend == BACKEND_LP_CORE:
|
||||
sources_expr = [
|
||||
f"ULP_LP_CORE_WAKEUP_SOURCE_{WAKEUP_SOURCES[s]}"
|
||||
for s in config.get(CONF_WAKEUP_SOURCES, [])
|
||||
]
|
||||
if period is not None:
|
||||
sources_expr.append("ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER")
|
||||
cg.add(var.set_wakeup_sources(cg.RawExpression(" | ".join(sources_expr))))
|
||||
cg.add(var.set_run_on_boot(config[CONF_RUN_ON_BOOT]))
|
||||
cg.add(var.set_wakeup_from_sleep(config[CONF_WAKEUP_FROM_SLEEP]))
|
||||
@@ -1,135 +0,0 @@
|
||||
#include "esp32_ulp.h"
|
||||
|
||||
#if defined(USE_ESP32) && (SOC_RISCV_COPROC_SUPPORTED || SOC_LP_CORE_SUPPORTED)
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_attr.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <esp_system.h>
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
#include <ulp_riscv.h>
|
||||
#endif
|
||||
|
||||
extern const uint8_t ULP_MAIN_BIN_START[] asm("_binary_ulp_main_bin_start");
|
||||
extern const size_t ULP_MAIN_BIN_LENGTH asm("ulp_main_bin_length");
|
||||
|
||||
namespace esphome::esp32_ulp {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "esp32_ulp");
|
||||
|
||||
// Survives deep sleep (RTC memory) so a program loaded before sleeping is known
|
||||
// to still be running on wake, however it was started.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static RTC_DATA_ATTR bool s_program_loaded = false;
|
||||
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
static constexpr const char *BACKEND = "ULP RISC-V";
|
||||
#else
|
||||
static constexpr const char *BACKEND = "LP core";
|
||||
#endif
|
||||
|
||||
void ESP32ULP::setup() {
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_ULP) {
|
||||
ESP_LOGI(TAG, "Woken from deep sleep by the %s", BACKEND);
|
||||
}
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
if (this->wakeup_period_us_ > 0) {
|
||||
esp_err_t err = ulp_set_wakeup_period(0, this->wakeup_period_us_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ulp_set_wakeup_period failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// After a deep sleep the program is still running in RTC memory; reloading
|
||||
// it would discard its state.
|
||||
if (esp_reset_reason() == ESP_RST_DEEPSLEEP && s_program_loaded) {
|
||||
this->loaded_ = true;
|
||||
} else if (this->run_on_boot_ && !this->load_and_run()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->wakeup_from_sleep_) {
|
||||
esp_err_t err = esp_sleep_enable_ulp_wakeup();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_sleep_enable_ulp_wakeup failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32ULP::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ESP32 ULP:\n"
|
||||
" Coprocessor: %s\n"
|
||||
" Program size: %u bytes\n"
|
||||
" Wakeup period: %" PRIu32 " us\n"
|
||||
" Run on boot: %s\n"
|
||||
" Wakeup from sleep: %s\n"
|
||||
" Loaded: %s",
|
||||
BACKEND, static_cast<unsigned>(ULP_MAIN_BIN_LENGTH), this->wakeup_period_us_, YESNO(this->run_on_boot_),
|
||||
YESNO(this->wakeup_from_sleep_), YESNO(this->loaded_));
|
||||
}
|
||||
|
||||
bool ESP32ULP::load_and_run() {
|
||||
const size_t size = ULP_MAIN_BIN_LENGTH;
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
// ulp_riscv_load_binary() overwrites RTC memory without stopping a running
|
||||
// program; the LP core variant stops the core itself.
|
||||
ulp_riscv_halt();
|
||||
esp_err_t err = ulp_riscv_load_binary(ULP_MAIN_BIN_START, size);
|
||||
#else
|
||||
esp_err_t err = ulp_lp_core_load_binary(ULP_MAIN_BIN_START, size);
|
||||
#endif
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Loading the program failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
if (!this->run_()) {
|
||||
return false;
|
||||
}
|
||||
this->loaded_ = true;
|
||||
s_program_loaded = true;
|
||||
ESP_LOGD(TAG, "Loaded and started %u byte program", static_cast<unsigned>(size));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP32ULP::run_() {
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
esp_err_t err = ulp_riscv_run();
|
||||
#else
|
||||
ulp_lp_core_cfg_t cfg = {};
|
||||
cfg.wakeup_source = this->wakeup_sources_;
|
||||
cfg.lp_timer_sleep_duration_us = this->wakeup_period_us_;
|
||||
esp_err_t err = ulp_lp_core_run(&cfg);
|
||||
#endif
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Starting the program failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESP32ULP::timer_stop() {
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
ulp_riscv_timer_stop();
|
||||
#else
|
||||
ulp_lp_core_stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32ULP::timer_resume() {
|
||||
if (!this->loaded_) {
|
||||
return;
|
||||
}
|
||||
#if SOC_RISCV_COPROC_SUPPORTED
|
||||
ulp_riscv_timer_resume();
|
||||
#else
|
||||
this->run_();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::esp32_ulp
|
||||
|
||||
#endif // USE_ESP32 && (SOC_RISCV_COPROC_SUPPORTED || SOC_LP_CORE_SUPPORTED)
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <soc/soc_caps.h>
|
||||
#if SOC_LP_CORE_SUPPORTED
|
||||
#include <ulp_lp_core.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32_ulp {
|
||||
|
||||
class ESP32ULP : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void set_wakeup_period(uint32_t period_us) { this->wakeup_period_us_ = period_us; }
|
||||
void set_run_on_boot(bool run) { this->run_on_boot_ = run; }
|
||||
void set_wakeup_from_sleep(bool enable) { this->wakeup_from_sleep_ = enable; }
|
||||
#if SOC_LP_CORE_SUPPORTED
|
||||
void set_wakeup_sources(uint32_t sources) { this->wakeup_sources_ = sources; }
|
||||
#endif
|
||||
|
||||
/// Load the embedded program into the coprocessor memory and start it.
|
||||
bool load_and_run();
|
||||
/// Stop the coprocessor timer so the program is not started again.
|
||||
void timer_stop();
|
||||
/// Resume the coprocessor timer.
|
||||
void timer_resume();
|
||||
|
||||
protected:
|
||||
bool run_();
|
||||
|
||||
uint32_t wakeup_period_us_{0};
|
||||
#if SOC_LP_CORE_SUPPORTED
|
||||
uint32_t wakeup_sources_{0};
|
||||
#endif
|
||||
bool run_on_boot_{true};
|
||||
bool wakeup_from_sleep_{true};
|
||||
bool loaded_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_ulp
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -7,7 +7,6 @@ from typing import Any
|
||||
|
||||
from esphome.build_helpers.native import native_backend
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -370,7 +369,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
_warn_platformio_toolchain,
|
||||
_validate_native_toolchain,
|
||||
set_core_data,
|
||||
network.require_ipv4(PLATFORM_ESP8266),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -263,7 +263,9 @@ void ESPHomeOTAComponent::udp_open_(UdpLink &link) {
|
||||
|
||||
bool ESPHomeOTAComponent::udp_start_() {
|
||||
// TCP now only carries a fallback byte; short read timeouts let the UDP link be checked in between
|
||||
struct timeval tv{0, UDP_COMMIT_POLL_MS * 1000};
|
||||
struct timeval tv {
|
||||
0, UDP_COMMIT_POLL_MS * 1000
|
||||
};
|
||||
this->client_->setsockopt(SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
const uint32_t start = millis();
|
||||
while (millis() - start < UDP_COMMIT_TIMEOUT) {
|
||||
|
||||
@@ -70,7 +70,8 @@ template<typename... Ts> class SendAction final : public Action<Ts...>, public P
|
||||
}
|
||||
|
||||
protected:
|
||||
void play(const Ts &...x) override { /* ignore - see play_complex */ }
|
||||
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||
}
|
||||
|
||||
void stop() override {
|
||||
this->sent_.stop();
|
||||
|
||||
@@ -8,7 +8,6 @@ from esphome.components.network import (
|
||||
get_network_priority,
|
||||
get_priority_interfaces_from_full_config,
|
||||
ip_address_literal,
|
||||
require_ipv4,
|
||||
)
|
||||
from esphome.config_helpers import (
|
||||
filter_source_files_from_defines,
|
||||
@@ -23,7 +22,6 @@ from esphome.const import (
|
||||
CONF_DNS2,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_ETHERNET,
|
||||
CONF_GATEWAY,
|
||||
CONF_ID,
|
||||
CONF_INTERRUPT_PIN,
|
||||
@@ -558,7 +556,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
upper=True,
|
||||
),
|
||||
_validate,
|
||||
require_ipv4(CONF_ETHERNET),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -289,10 +289,10 @@ class EthernetComponent final : public Component {
|
||||
#endif
|
||||
|
||||
// Group all 8-bit members together
|
||||
uint8_t clk_pin_{0}; // Must match DEFAULT_CLK_PIN in __init__.py
|
||||
uint8_t phy_addr_{0}; // Must match DEFAULT_PHY_ADDR in __init__.py
|
||||
uint8_t mdc_pin_{23}; // Must match DEFAULT_MDC_PIN in __init__.py
|
||||
uint8_t mdio_pin_{18}; // Must match DEFAULT_MDIO_PIN in __init__.py
|
||||
uint8_t clk_pin_{0}; // Must match DEFAULT_CLK_PIN in __init__.py
|
||||
uint8_t phy_addr_{0}; // Must match DEFAULT_PHY_ADDR in __init__.py
|
||||
uint8_t mdc_pin_{23}; // Must match DEFAULT_MDC_PIN in __init__.py
|
||||
uint8_t mdio_pin_{18}; // Must match DEFAULT_MDIO_PIN in __init__.py
|
||||
#endif // USE_ETHERNET_SPI
|
||||
|
||||
// ESP32 pointers
|
||||
|
||||
@@ -37,7 +37,6 @@ from esphome.core.entity_helpers import (
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
DOMAIN = "fan"
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -212,15 +211,6 @@ def validate_preset_modes(value):
|
||||
return value
|
||||
|
||||
|
||||
def set_preset_modes(var: MockObj, preset_modes: list[str]) -> None:
|
||||
"""Point the fan at a shared flash table of preset modes."""
|
||||
if preset_modes:
|
||||
table = cg.shared_progmem_array(
|
||||
"fan_preset_modes", cg.const_char_ptr, preset_modes
|
||||
)
|
||||
cg.add(var.set_supported_preset_modes_static(table, len(preset_modes)))
|
||||
|
||||
|
||||
# The edge and set triggers keep their classes; each tracks the previous value.
|
||||
_TRIGGER_AUTOMATIONS = (
|
||||
(CONF_ON_TURN_ON, []),
|
||||
|
||||
@@ -8,10 +8,20 @@ namespace esphome::fan {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "fan");
|
||||
|
||||
static const FanPresetModes EMPTY_PRESET_MODES; // NOLINT
|
||||
// Compat: shared empty vector for getter when no preset modes are set.
|
||||
// Remove in 2026.11.0 when deprecated FanTraits setters are removed
|
||||
// and getter can return const vector * instead of const vector &.
|
||||
static const std::vector<const char *> EMPTY_PRESET_MODES; // NOLINT
|
||||
|
||||
const FanPresetModes &FanTraits::supported_preset_modes() const {
|
||||
return this->preset_modes_ != nullptr ? *this->preset_modes_ : EMPTY_PRESET_MODES;
|
||||
const std::vector<const char *> &FanTraits::supported_preset_modes() const {
|
||||
if (this->preset_modes_) {
|
||||
return *this->preset_modes_;
|
||||
}
|
||||
// Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0 (change return to const vector *).
|
||||
if (!this->compat_preset_modes_.empty()) {
|
||||
return this->compat_preset_modes_;
|
||||
}
|
||||
return EMPTY_PRESET_MODES;
|
||||
}
|
||||
|
||||
// Fan direction strings indexed by FanDirection enum (0-1): FORWARD, REVERSE, plus UNKNOWN
|
||||
@@ -151,12 +161,16 @@ const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) {
|
||||
if (preset_mode == nullptr || len == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const char *mode : this->supported_preset_modes_) {
|
||||
if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') {
|
||||
return mode;
|
||||
if (this->supported_preset_modes_) {
|
||||
for (const char *mode : *this->supported_preset_modes_) {
|
||||
if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
// Fallback for deprecated path: external components may set modes on FanTraits directly
|
||||
return this->get_traits().find_preset_mode(preset_mode, len);
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const char *preset_mode, size_t len) {
|
||||
@@ -277,10 +291,25 @@ void Fan::save_state_() {
|
||||
state.preset_mode = FanRestoreState::NO_PRESET;
|
||||
|
||||
if (this->has_preset_mode()) {
|
||||
for (size_t i = 0; i < this->supported_preset_modes_.size(); i++) {
|
||||
if (this->supported_preset_modes_[i] == this->preset_mode_) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
if (this->supported_preset_modes_) {
|
||||
// New path: search Fan-owned vector directly
|
||||
for (size_t i = 0; i < this->supported_preset_modes_->size(); i++) {
|
||||
if ((*this->supported_preset_modes_)[i] == this->preset_mode_) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Compat: fall back to traits for deprecated path. Remove in 2026.11.0.
|
||||
// Pointer comparison works because preset_mode_ and the compat vector both
|
||||
// hold pointers to string literals in .rodata (stable addresses).
|
||||
auto traits = this->get_traits();
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
for (size_t i = 0; i < preset_modes.size(); i++) {
|
||||
if (preset_modes[i] == this->preset_mode_) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -130,19 +128,12 @@ class Fan : public EntityBase {
|
||||
|
||||
virtual FanTraits get_traits() = 0;
|
||||
|
||||
/// Set the supported preset modes; the list is copied (stored on Fan, referenced by FanTraits).
|
||||
/// Set the supported preset modes (stored on Fan, referenced by FanTraits via pointer).
|
||||
void set_supported_preset_modes(std::initializer_list<const char *> preset_modes) {
|
||||
this->supported_preset_modes_.assign_copy(preset_modes.begin(), preset_modes.size());
|
||||
this->ensure_preset_modes_().assign(preset_modes.begin(), preset_modes.end());
|
||||
}
|
||||
void set_supported_preset_modes(const std::vector<const char *> &preset_modes) {
|
||||
this->supported_preset_modes_.assign_copy(preset_modes.data(), preset_modes.size());
|
||||
}
|
||||
void set_supported_preset_modes(const FanPresetModes &preset_modes) {
|
||||
this->supported_preset_modes_.assign_copy(preset_modes.data(), preset_modes.size());
|
||||
}
|
||||
/// Points at a static table that outlives the fan (codegen); call before any runtime copy.
|
||||
void set_supported_preset_modes_static(const char *const *preset_modes, size_t count) {
|
||||
this->supported_preset_modes_.assign_static(preset_modes, count);
|
||||
this->ensure_preset_modes_() = preset_modes;
|
||||
}
|
||||
|
||||
/// Set the restore mode of this fan.
|
||||
@@ -183,14 +174,26 @@ class Fan : public EntityBase {
|
||||
const char *find_preset_mode_(const char *preset_mode, size_t len);
|
||||
|
||||
/// Wire the Fan-owned preset modes pointer into the given traits object.
|
||||
void wire_preset_modes_(FanTraits &traits) { traits.set_supported_preset_modes_(&this->supported_preset_modes_); }
|
||||
void wire_preset_modes_(FanTraits &traits) {
|
||||
if (this->supported_preset_modes_) {
|
||||
traits.set_supported_preset_modes_(this->supported_preset_modes_);
|
||||
}
|
||||
}
|
||||
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
FanRestoreMode restore_mode_{FanRestoreMode::NO_RESTORE};
|
||||
|
||||
private:
|
||||
FanPresetModes supported_preset_modes_;
|
||||
/// Lazy-allocate preset modes vector (never freed — entity lives forever).
|
||||
std::vector<const char *> &ensure_preset_modes_() {
|
||||
if (!this->supported_preset_modes_) {
|
||||
this->supported_preset_modes_ = new std::vector<const char *>(); // NOLINT
|
||||
}
|
||||
return *this->supported_preset_modes_;
|
||||
}
|
||||
|
||||
std::vector<const char *> *supported_preset_modes_{nullptr};
|
||||
const char *preset_mode_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::fan {
|
||||
|
||||
class Fan; // Forward declaration
|
||||
|
||||
/// Preset modes: a flash table from codegen, or an owned copy set at runtime.
|
||||
using FanPresetModes = ConstVector<const char *, true>;
|
||||
|
||||
class FanTraits {
|
||||
friend class Fan; // Allow Fan to access protected pointer setter
|
||||
|
||||
@@ -35,11 +33,32 @@ class FanTraits {
|
||||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
void set_direction(bool direction) { this->direction_ = direction; }
|
||||
/// Empty when the fan has no preset modes. Set them on the Fan entity.
|
||||
const FanPresetModes &supported_preset_modes() const;
|
||||
// Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *.
|
||||
const std::vector<const char *> &supported_preset_modes() const;
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_preset_modes(std::initializer_list<const char *> preset_modes) {
|
||||
// Compat: store in owned vector. Copies copy the vector (deprecated path still copies this vector).
|
||||
this->compat_preset_modes_ = preset_modes;
|
||||
}
|
||||
// Remove before 2026.11.0
|
||||
ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0")
|
||||
void set_supported_preset_modes(const std::vector<const char *> &preset_modes) {
|
||||
this->compat_preset_modes_ = preset_modes;
|
||||
}
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_supported_preset_modes(const std::vector<std::string> &preset_modes) = delete;
|
||||
void set_supported_preset_modes(std::initializer_list<std::string> preset_modes) = delete;
|
||||
|
||||
/// Return if preset modes are supported
|
||||
bool supports_preset_modes() const { return this->preset_modes_ != nullptr && !this->preset_modes_->empty(); }
|
||||
bool supports_preset_modes() const {
|
||||
// Same precedence as supported_preset_modes() getter
|
||||
if (this->preset_modes_) {
|
||||
return !this->preset_modes_->empty();
|
||||
}
|
||||
return !this->compat_preset_modes_.empty();
|
||||
}
|
||||
/// Find and return the matching preset mode pointer from supported modes, or nullptr if not found.
|
||||
const char *find_preset_mode(const char *preset_mode) const {
|
||||
return this->find_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0);
|
||||
@@ -48,7 +67,9 @@ class FanTraits {
|
||||
if (preset_mode == nullptr || len == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const char *mode : this->supported_preset_modes()) {
|
||||
// Check pointer-based storage (new path) then compat owned vector (deprecated path)
|
||||
const auto &modes = this->preset_modes_ ? *this->preset_modes_ : this->compat_preset_modes_;
|
||||
for (const char *mode : modes) {
|
||||
if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') {
|
||||
return mode;
|
||||
}
|
||||
@@ -58,14 +79,18 @@ class FanTraits {
|
||||
|
||||
protected:
|
||||
/// Set the preset modes pointer (only Fan::wire_preset_modes_() should call this).
|
||||
void set_supported_preset_modes_(const FanPresetModes *preset_modes) { this->preset_modes_ = preset_modes; }
|
||||
void set_supported_preset_modes_(const std::vector<const char *> *preset_modes) {
|
||||
this->preset_modes_ = preset_modes;
|
||||
}
|
||||
|
||||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
int speed_count_{};
|
||||
const FanPresetModes *preset_modes_{nullptr}; // owned by the Fan entity
|
||||
const std::vector<const char *> *preset_modes_{nullptr};
|
||||
// Compat: owned storage for deprecated setters. Copies copy the vector (copies include this vector).
|
||||
// Remove in 2026.11.0.
|
||||
std::vector<const char *> compat_preset_modes_;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<FanTraits>, "FanTraits is returned by value from get_traits()");
|
||||
|
||||
} // namespace esphome::fan
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, output
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from esphome.const import CONF_CHANNEL
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_GP8403_ID, GP8403Component, gp8403_ns
|
||||
from .. import CONF_GP8403_ID, GP8403Component
|
||||
|
||||
DEPENDENCIES = ["gp8403"]
|
||||
|
||||
GP8403Output = gp8403_ns.class_(
|
||||
"GP8403Output", cg.Component, i2c.I2CDevice, output.FloatOutput
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GP8403Output),
|
||||
cv.GenerateID(): cv.declare_id(output.ApplyFloatOutput),
|
||||
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403Component),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_GP8403_ID])
|
||||
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
await output.sub_float_outputs(config, parent=config[CONF_GP8403_ID]).create(
|
||||
config, None, config[CONF_CHANNEL], apply="write_state(state, index)"
|
||||
)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "gp8403_output.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::gp8403 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "gp8403.output");
|
||||
|
||||
void GP8403Output::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"GP8403 Output:\n"
|
||||
" Channel: %u",
|
||||
this->channel_);
|
||||
}
|
||||
|
||||
void GP8403Output::write_state(float state) { this->parent_->write_state(state, this->channel_); }
|
||||
|
||||
} // namespace esphome::gp8403
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "esphome/components/gp8403/gp8403.h"
|
||||
|
||||
namespace esphome::gp8403 {
|
||||
|
||||
class GP8403Output final : public Component, public output::FloatOutput, public Parented<GP8403Component> {
|
||||
public:
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA - 1; }
|
||||
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||
void write_state(float state) override;
|
||||
|
||||
protected:
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
} // namespace esphome::gp8403
|
||||
@@ -16,7 +16,6 @@ from esphome.const import (
|
||||
UNIT_KILOMETER_PER_HOUR,
|
||||
UNIT_METER,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONF_GPS_ID = "gps_id"
|
||||
@@ -41,16 +40,6 @@ GPS = gps_ns.class_("GPS", cg.PollingComponent, uart.UARTDevice)
|
||||
GPSListener = gps_ns.class_("GPSListener")
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
_request_listener_slot = cg.slot_counter("GPS_LISTENER_COUNT")
|
||||
|
||||
|
||||
async def register_listener(hub: MockObj, var: MockObj) -> None:
|
||||
"""Register a listener with its GPS and count it for the compile-time list size."""
|
||||
_request_listener_slot(str(hub))
|
||||
cg.add(hub.register_listener(var))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -84,11 +84,9 @@ void GPS::loop() {
|
||||
ESP_LOGV(TAG, "HDOP: %.3f", this->hdop_);
|
||||
}
|
||||
|
||||
#ifdef GPS_LISTENER_COUNT
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_update(this->tiny_gps_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <TinyGPSPlus.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::gps {
|
||||
|
||||
class GPS;
|
||||
@@ -31,12 +32,10 @@ class GPS final : public PollingComponent, public uart::UARTDevice {
|
||||
void set_satellites_sensor(sensor::Sensor *satellites_sensor) { this->satellites_sensor_ = satellites_sensor; }
|
||||
void set_hdop_sensor(sensor::Sensor *hdop_sensor) { this->hdop_sensor_ = hdop_sensor; }
|
||||
|
||||
#ifdef GPS_LISTENER_COUNT
|
||||
void register_listener(GPSListener *listener) {
|
||||
listener->parent_ = this;
|
||||
this->listeners_.push_back(listener);
|
||||
}
|
||||
#endif
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void dump_config() override;
|
||||
@@ -64,9 +63,7 @@ class GPS final : public PollingComponent, public uart::UARTDevice {
|
||||
sensor::Sensor *hdop_sensor_{nullptr};
|
||||
|
||||
TinyGPSPlus tiny_gps_;
|
||||
#ifdef GPS_LISTENER_COUNT
|
||||
StaticVector<GPSListener *, GPS_LISTENER_COUNT> listeners_;
|
||||
#endif
|
||||
std::vector<GPSListener *> listeners_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::gps
|
||||
|
||||
@@ -4,7 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import CONF_GPS_ID, GPS, GPSListener, gps_ns, register_listener
|
||||
from .. import CONF_GPS_ID, GPS, GPSListener, gps_ns
|
||||
|
||||
DEPENDENCIES = ["gps"]
|
||||
|
||||
@@ -26,4 +26,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_GPS_ID])
|
||||
await register_listener(paren, var)
|
||||
cg.add(paren.register_listener(var))
|
||||
|
||||
@@ -137,7 +137,7 @@ class HaierClimateBase : public esphome::Component,
|
||||
esphome::optional<float> target_temperature;
|
||||
esphome::optional<esphome::climate::ClimatePreset> preset;
|
||||
bool valid;
|
||||
HvacSettings() : valid(false) {};
|
||||
HvacSettings() : valid(false){};
|
||||
HvacSettings(const HvacSettings &) = default;
|
||||
HvacSettings &operator=(const HvacSettings &) = default;
|
||||
void reset();
|
||||
|
||||
@@ -65,9 +65,9 @@ PROGMEM_STRING_TABLE(
|
||||
"Formaldehyde sensor failure", "VOC sensor failure", "CO2 sensor failure", "Firewall failure", "Unknown");
|
||||
|
||||
HonClimate::HonClimate()
|
||||
: cleaning_status_(CleaningState::NO_CLEANING),
|
||||
got_valid_outdoor_temp_(false),
|
||||
active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} {
|
||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, 0x00} {
|
||||
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
|
||||
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.add_variable(var.set_pin_b, config[CONF_PIN_B])
|
||||
await cg.add_variable(var.set_enable_pin, config.get(CONF_ENABLE_PIN))
|
||||
|
||||
fan.set_preset_modes(var, config.get(CONF_PRESET_MODES, []))
|
||||
cg.add_optional(var.set_preset_modes, config.get(CONF_PRESET_MODES))
|
||||
|
||||
@@ -18,8 +18,6 @@ class HBridgeFan final : public Component, public fan::Fan {
|
||||
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
|
||||
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
|
||||
void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; }
|
||||
// Remove before 2027.5.0
|
||||
ESPDEPRECATED("Use set_supported_preset_modes() instead. Removed in 2027.5.0", "2026.11.0")
|
||||
void set_preset_modes(std::initializer_list<const char *> presets) { this->set_supported_preset_modes(presets); }
|
||||
|
||||
void setup() override;
|
||||
|
||||
@@ -52,9 +52,7 @@ template<Protocol P> HeatpumpIR *create_heatpump_ir() {
|
||||
|
||||
// Codegen calls only the configured protocols, so the linker drops the other classes
|
||||
#define HEATPUMPIR_PROTOCOL(protocol, cls) \
|
||||
template<> struct ProtocolClass<protocol> { \
|
||||
using type = cls; \
|
||||
}; \
|
||||
template<> struct ProtocolClass<protocol> { using type = cls; }; \
|
||||
template HeatpumpIR *create_heatpump_ir<protocol>();
|
||||
|
||||
HEATPUMPIR_PROTOCOL(PROTOCOL_AUX, AUXHeatpumpIR)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import sys
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import network
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MAC_ADDRESS,
|
||||
@@ -45,7 +44,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
# is no PlatformIO backend, so a CLI --toolchain must name this one
|
||||
cv.resolve_toolchain("host", (Toolchain.HOST,), Toolchain.HOST),
|
||||
set_core_data,
|
||||
network.require_ipv4(PLATFORM_HOST),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
((std::max) (8u, std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() - 1 : 0))
|
||||
((std::max)(8u, std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() - 1 : 0))
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_RECV_FLAGS
|
||||
@@ -182,11 +182,11 @@ using ssize_t = long;
|
||||
#endif // _MSC_VER
|
||||
|
||||
#ifndef S_ISREG
|
||||
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
|
||||
#define S_ISREG(m) (((m) &S_IFREG) == S_IFREG)
|
||||
#endif // S_ISREG
|
||||
|
||||
#ifndef S_ISDIR
|
||||
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
|
||||
#define S_ISDIR(m) (((m) &S_IFDIR) == S_IFDIR)
|
||||
#endif // S_ISDIR
|
||||
|
||||
#ifndef NOMINMAX
|
||||
@@ -3773,7 +3773,7 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length, bool
|
||||
do {
|
||||
constexpr size_t max_avail_in = (std::numeric_limits<decltype(strm_.avail_in)>::max)();
|
||||
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>((std::min) (data_length, max_avail_in));
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>((std::min)(data_length, max_avail_in));
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
data_length -= strm_.avail_in;
|
||||
@@ -3829,7 +3829,7 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
||||
do {
|
||||
constexpr size_t max_avail_in = (std::numeric_limits<decltype(strm_.avail_in)>::max)();
|
||||
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>((std::min) (data_length, max_avail_in));
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>((std::min)(data_length, max_avail_in));
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
data_length -= strm_.avail_in;
|
||||
@@ -4067,7 +4067,7 @@ inline bool read_content_with_length(Stream &strm, uint64_t len, Progress progre
|
||||
uint64_t r = 0;
|
||||
while (r < len) {
|
||||
auto read_len = static_cast<size_t>(len - r);
|
||||
auto n = strm.read(buf, (std::min) (read_len, CPPHTTPLIB_RECV_BUFSIZ));
|
||||
auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
|
||||
if (n <= 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -4092,7 +4092,7 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
|
||||
uint64_t r = 0;
|
||||
while (r < len) {
|
||||
auto read_len = static_cast<size_t>(len - r);
|
||||
auto n = strm.read(buf, (std::min) (read_len, CPPHTTPLIB_RECV_BUFSIZ));
|
||||
auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
|
||||
if (n <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -5068,8 +5068,8 @@ inline bool range_error(Request &req, Response &res) {
|
||||
}
|
||||
}
|
||||
|
||||
prev_first_pos = (std::max) (prev_first_pos, first_pos);
|
||||
prev_last_pos = (std::max) (prev_last_pos, last_pos);
|
||||
prev_first_pos = (std::max)(prev_first_pos, first_pos);
|
||||
prev_last_pos = (std::max)(prev_last_pos, last_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5718,9 +5718,9 @@ inline bool SocketStream::is_writable() const {
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
#ifdef _WIN32
|
||||
size = (std::min) (size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#else
|
||||
size = (std::min) (size, static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
|
||||
#endif
|
||||
|
||||
if (read_buff_off_ < read_buff_content_size_) {
|
||||
@@ -5767,7 +5767,7 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
size = (std::min) (size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
size = (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
#endif
|
||||
|
||||
return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
|
||||
@@ -9142,8 +9142,8 @@ inline bool SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) cons
|
||||
|
||||
auto type = GEN_DNS;
|
||||
|
||||
struct in6_addr addr6{};
|
||||
struct in_addr addr{};
|
||||
struct in6_addr addr6 {};
|
||||
struct in_addr addr {};
|
||||
size_t addr_len = 0;
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
@@ -107,10 +107,13 @@ def _set_stream_limits(config: ConfigType) -> ConfigType:
|
||||
)(config)
|
||||
return config
|
||||
|
||||
# The configured bits per sample sets the I2S slot width, but the speaker converts streams of other widths
|
||||
# before clocking them out (see start_i2s_driver), widening them on the original ESP32. Advertise 8- to 32-bit
|
||||
# so those streams are accepted rather than forcing an upstream conversion.
|
||||
min_bits_per_sample = 8
|
||||
# The original ESP32 cannot lay out sub-16-bit slots that match ESPHome's packed audio, so the smallest
|
||||
# stream it accepts is 16-bit (see start_i2s_driver); the other variants handle 8-bit.
|
||||
min_bits_per_sample = 16 if esp32.get_esp32_variant() == esp32.VARIANT_ESP32 else 8
|
||||
|
||||
# The configured bits per sample sets the I2S slot width, but the speaker narrows wider streams down to it
|
||||
# in place before clocking them out (see start_i2s_driver). Advertise up to 32-bit so those wider streams
|
||||
# are accepted rather than forcing an upstream conversion.
|
||||
max_bits_per_sample = 32
|
||||
|
||||
if config[CONF_I2S_MODE] == CONF_PRIMARY:
|
||||
@@ -150,12 +153,10 @@ def _validate_esp32_variant(config: ConfigType) -> ConfigType:
|
||||
if variant not in INTERNAL_DAC_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not have an internal DAC")
|
||||
elif variant == esp32.VARIANT_ESP32 and config[CONF_BITS_PER_SAMPLE] == 8:
|
||||
# An 8-bit slot is rejected on the original ESP32: no known DAC needs one, and it is untested there.
|
||||
# 8-bit streams still work; they are widened to the 16- or 32-bit slot (see start_i2s_driver).
|
||||
raise cv.Invalid(
|
||||
f"{CONF_BITS_PER_SAMPLE} cannot be 8bit on the original ESP32",
|
||||
path=[CONF_BITS_PER_SAMPLE],
|
||||
)
|
||||
# The original ESP32 I2S peripheral packs each sample into a whole number of 16-bit words, so an
|
||||
# 8-bit slot does not line up with ESPHome's tightly packed audio (see start_i2s_driver). Reject it
|
||||
# at config time rather than emitting corrupted output at runtime.
|
||||
raise cv.Invalid("8-bit audio is not supported on the original ESP32")
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -208,8 +208,8 @@ class I2SAudioSpeakerBase : public I2SAudioOut, public speaker::Speaker, public
|
||||
|
||||
audio::AudioStreamInfo current_stream_info_; // Format of the audio in the ring buffer (the I2S input)
|
||||
// Format actually clocked out of the I2S peripheral. Same channel count and sample rate as
|
||||
// current_stream_info_, but the bits per sample may be converted to the speaker's configured slot bit width.
|
||||
// Set by start_i2s_driver before the speaker task starts.
|
||||
// current_stream_info_, but the bits per sample may be narrower when the incoming stream is wider than
|
||||
// the speaker's configured slot bit width. Set by start_i2s_driver before the speaker task starts.
|
||||
audio::AudioStreamInfo output_stream_info_;
|
||||
|
||||
gpio_num_t dout_pin_;
|
||||
|
||||
@@ -38,11 +38,6 @@ static constexpr size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT * 2;
|
||||
// DMA_BUFFER_DURATION_MS, so a multiple of that gives plenty of slack against scheduling jitter
|
||||
// without masking real failures.
|
||||
static constexpr TickType_t WRITE_TIMEOUT_TICKS = pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS * (DMA_BUFFERS_COUNT + 1));
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// Stack buffer that widened audio passes through on the original ESP32. Small enough for the speaker task's stack,
|
||||
// large enough that the extra i2s_channel_write calls cost little.
|
||||
static constexpr size_t WIDEN_BUFFER_BYTES = 512;
|
||||
#endif
|
||||
|
||||
// Requested frames per DMA buffer for the given stream, clamped so the byte size stays within the ESP-IDF
|
||||
// maximum DMA descriptor size. This is only the value handed to the channel config: ESP-IDF may still adjust
|
||||
@@ -71,8 +66,9 @@ void I2SAudioSpeaker::dump_config() {
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Communication format: %s", fmt_str);
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
// The width of each I2S slot. Wider streams are narrowed to it. Narrower streams are padded into the slot by
|
||||
// the peripheral, except on the original ESP32, where they are widened to it.
|
||||
// The width of each I2S slot. It is also the narrowing ceiling: streams wider than this are narrowed to
|
||||
// it. A stream narrower than the slot is left at its own width and clocked into the wider slot, so this
|
||||
// is not necessarily the sample data width (which depends on the incoming stream).
|
||||
ESP_LOGCONFIG(TAG, " Slot bit width: %u", (unsigned) static_cast<uint32_t>(this->slot_bit_width_));
|
||||
}
|
||||
}
|
||||
@@ -96,14 +92,11 @@ void I2SAudioSpeaker::run_speaker_task() {
|
||||
const size_t ring_buffer_size =
|
||||
(this->current_stream_info_.ms_to_bytes(ring_buffer_duration) / source_alignment) * source_alignment;
|
||||
|
||||
// Per-frame byte widths and whether the task must convert the bit depth before writing to the I2S peripheral.
|
||||
// Per-frame byte widths and whether the task must narrow the bit depth before writing to the I2S peripheral.
|
||||
const uint8_t channels = this->current_stream_info_.get_channels();
|
||||
const uint8_t input_bytes_per_sample = this->current_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint8_t output_bytes_per_sample = this->output_stream_info_.get_bits_per_sample() / 8;
|
||||
const bool narrowing = input_bytes_per_sample > output_bytes_per_sample;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
const bool widening = input_bytes_per_sample < output_bytes_per_sample;
|
||||
#endif
|
||||
const bool narrowing = input_bytes_per_sample != output_bytes_per_sample;
|
||||
|
||||
// ESP-IDF may allocate smaller (or cache-line-rounded) DMA buffers than dma_buffer_frames() requested: it
|
||||
// clamps each descriptor to the max DMA descriptor size and, on targets that route internal memory through
|
||||
@@ -330,26 +323,20 @@ void I2SAudioSpeaker::run_speaker_task() {
|
||||
const size_t output_bytes = this->output_stream_info_.frames_to_bytes(frames_to_write);
|
||||
|
||||
uint8_t *chunk = audio_source->mutable_data();
|
||||
size_t bw = 0;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
if (widening) {
|
||||
bw = this->write_widened_(chunk, frames_to_write);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (narrowing) {
|
||||
// Narrow the bit depth in place: output exactly aliases input with the same channel count and a
|
||||
// smaller width, which copy_frames handles as a single forward pass. Only the frames about to be
|
||||
// consumed are overwritten, so any unprocessed tail stays intact for the next iteration.
|
||||
esp_audio_libs::pcm_convert::copy_frames(chunk, chunk, input_bytes_per_sample, channels,
|
||||
output_bytes_per_sample, channels, frames_to_write);
|
||||
}
|
||||
this->swap_esp32_mono_samples_(chunk, output_bytes);
|
||||
i2s_channel_write(this->tx_handle_, chunk, output_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
if (narrowing) {
|
||||
// Narrow the bit depth in place: output exactly aliases input with the same channel count and a
|
||||
// smaller width, which copy_frames handles as a single forward pass. Only the frames about to be
|
||||
// consumed are overwritten, so any unprocessed tail stays intact for the next iteration.
|
||||
esp_audio_libs::pcm_convert::copy_frames(chunk, chunk, input_bytes_per_sample, channels,
|
||||
output_bytes_per_sample, channels, frames_to_write);
|
||||
}
|
||||
this->swap_esp32_mono_samples_(chunk, output_bytes);
|
||||
|
||||
size_t bw = 0;
|
||||
i2s_channel_write(this->tx_handle_, chunk, output_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
if (bw != output_bytes) {
|
||||
// A short write breaks DMA descriptor alignment for every subsequent event. Drop the chunk rather
|
||||
// than retry it: it may already have been converted in place.
|
||||
// than retry it: it was already narrowed in place.
|
||||
ESP_LOGE(TAG, "Partial DMA write (%u of %u bytes), resyncing DMA lockstep", (unsigned) bw,
|
||||
(unsigned) output_bytes);
|
||||
audio_source->consume(input_bytes);
|
||||
@@ -429,26 +416,26 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver(audio::AudioStreamInfo &audio_stream
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// Compute the output width so the driver, DMA buffers, and the task's conversion all agree. A stream wider than
|
||||
// the configured slot is narrowed to it, and a narrower stream keeps its width for the peripheral to pad into the
|
||||
// slot. The original ESP32 cannot clock a slot wider than its data, so there every stream is converted to the
|
||||
// slot's memory width instead.
|
||||
// When the stream is wider than the configured slot bit width, the speaker task narrows each frame in place
|
||||
// before handing it to the I2S peripheral. Compute the output format here so the driver, DMA buffers, and
|
||||
// the task's conversion all agree on the clocked-out width. A stream no wider than the slot width is passed
|
||||
// through unchanged (the slot may still be wider than the data, the existing behavior).
|
||||
uint8_t output_bits_per_sample = audio_stream_info.get_bits_per_sample();
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
output_bits_per_sample = this->slot_memory_bits_();
|
||||
#else
|
||||
const uint8_t configured_bits = static_cast<uint8_t>(this->slot_bit_width_);
|
||||
if (output_bits_per_sample > configured_bits) {
|
||||
output_bits_per_sample = configured_bits;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
this->output_stream_info_ = audio::AudioStreamInfo(output_bits_per_sample, audio_stream_info.get_channels(),
|
||||
audio_stream_info.get_sample_rate());
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// Only a slot left at AUTO can reach this; every configured slot is converted to 16- or 32-bit samples above
|
||||
// The original ESP32 I2S peripheral stores each sample in a whole number of 16-bit words (a 24-bit sample
|
||||
// occupies 4 bytes in the DMA buffer, an 8-bit sample 2 bytes), but ESPHome's audio pipeline packs samples
|
||||
// tightly (3 bytes for 24-bit, 1 for 8-bit). The two layouts only line up when the bit depth is a multiple
|
||||
// of 16. The check is on the output width since that is what reaches the peripheral; a wider input is fine
|
||||
// as long as it narrows to a 16- or 32-bit slot.
|
||||
if (output_bits_per_sample % 16 != 0) {
|
||||
ESP_LOGE(TAG, "ESP32 supports only 16- or 32-bit output, got %u-bit", (unsigned) output_bits_per_sample);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -500,8 +487,8 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver(audio::AudioStreamInfo &audio_stream
|
||||
|
||||
#ifdef USE_I2S_AUDIO_FULL_DUPLEX
|
||||
audio::AudioStreamInfo I2SAudioSpeaker::full_duplex_stream_info_() const {
|
||||
return audio::AudioStreamInfo(this->slot_memory_bits_(), this->slot_mode_ == I2S_SLOT_MODE_STEREO ? 2 : 1,
|
||||
this->sample_rate_);
|
||||
return audio::AudioStreamInfo(static_cast<uint8_t>(this->slot_bit_width_),
|
||||
this->slot_mode_ == I2S_SLOT_MODE_STEREO ? 2 : 1, this->sample_rate_);
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::build_full_duplex_config(i2s_chan_config_t &chan_cfg, i2s_std_config_t &std_cfg) {
|
||||
@@ -543,14 +530,8 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
slot_mask = I2S_STD_SLOT_BOTH;
|
||||
}
|
||||
|
||||
// Configure the data bit width from the output (post-conversion) format, which is what is clocked out.
|
||||
i2s_data_bit_width_t data_bit_width = (i2s_data_bit_width_t) output_stream_info.get_bits_per_sample();
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// The original ESP32 clocks data at the slot width; a 24-bit slot sends the top 24 bits of each 32-bit sample
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
data_bit_width = (i2s_data_bit_width_t) this->slot_bit_width_;
|
||||
}
|
||||
#endif
|
||||
// Configure the data bit width from the output (post-narrowing) format, which is what is clocked out.
|
||||
const i2s_data_bit_width_t data_bit_width = (i2s_data_bit_width_t) output_stream_info.get_bits_per_sample();
|
||||
i2s_std_slot_config_t slot_cfg;
|
||||
switch (this->i2s_comm_fmt_) {
|
||||
case I2SCommFmt::PCM:
|
||||
@@ -564,10 +545,23 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// There seems to be a bug on the ESP32 (non-variant) platform where setting the slot bit width higher than the
|
||||
// bits per sample causes the audio to play too fast. Setting the ws_width to the configured slot bit width seems
|
||||
// to make it play at the correct speed while sending more bits per slot.
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
uint32_t configured_bit_width = static_cast<uint32_t>(this->slot_bit_width_);
|
||||
slot_cfg.ws_width = configured_bit_width;
|
||||
if (configured_bit_width > 16) {
|
||||
slot_cfg.msb_right = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
slot_cfg.slot_bit_width = this->slot_bit_width_;
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
slot_cfg.ws_width = static_cast<uint32_t>(this->slot_bit_width_);
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32
|
||||
slot_cfg.slot_mask = slot_mask;
|
||||
|
||||
i2s_std_gpio_config_t gpio_cfg = this->parent_->get_pin_config();
|
||||
@@ -580,45 +574,6 @@ void I2SAudioSpeaker::build_i2s_config_(const audio::AudioStreamInfo &output_str
|
||||
};
|
||||
}
|
||||
|
||||
uint8_t I2SAudioSpeaker::slot_memory_bits_() const {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// The original ESP32 stores a 24-bit slot's samples in 32-bit words
|
||||
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_24BIT) {
|
||||
return 32;
|
||||
}
|
||||
#endif
|
||||
return static_cast<uint8_t>(this->slot_bit_width_);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
size_t I2SAudioSpeaker::write_widened_(const uint8_t *input, uint32_t frames) {
|
||||
alignas(4) uint8_t widened[WIDEN_BUFFER_BYTES];
|
||||
const uint8_t channels = this->current_stream_info_.get_channels();
|
||||
const uint8_t input_bytes_per_sample = this->current_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint8_t output_bytes_per_sample = this->output_stream_info_.get_bits_per_sample() / 8;
|
||||
const uint32_t frames_per_pass = WIDEN_BUFFER_BYTES / this->output_stream_info_.frames_to_bytes(1);
|
||||
|
||||
size_t bytes_written = 0;
|
||||
while (frames > 0) {
|
||||
const uint32_t pass_frames = std::min(frames, frames_per_pass);
|
||||
esp_audio_libs::pcm_convert::copy_frames(input, widened, input_bytes_per_sample, channels, output_bytes_per_sample,
|
||||
channels, pass_frames);
|
||||
const size_t pass_bytes = this->output_stream_info_.frames_to_bytes(pass_frames);
|
||||
// 8-bit mono widened to 16-bit mono needs the pair swap; passes hold whole pairs since writes are pair-aligned
|
||||
this->swap_esp32_mono_samples_(widened, pass_bytes);
|
||||
size_t bw = 0;
|
||||
i2s_channel_write(this->tx_handle_, widened, pass_bytes, &bw, WRITE_TIMEOUT_TICKS);
|
||||
bytes_written += bw;
|
||||
if (bw != pass_bytes) {
|
||||
break;
|
||||
}
|
||||
input += this->current_stream_info_.frames_to_bytes(pass_frames);
|
||||
frames -= pass_frames;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
#endif // USE_ESP32_VARIANT_ESP32
|
||||
|
||||
} // namespace esphome::i2s_audio
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -38,17 +38,6 @@ class I2SAudioSpeaker final : public I2SAudioSpeakerBase {
|
||||
audio::AudioStreamInfo full_duplex_stream_info_() const;
|
||||
#endif
|
||||
|
||||
/// @brief Bits per sample that the configured slot takes in DMA memory.
|
||||
uint8_t slot_memory_bits_() const;
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
/// @brief Widens frames to the output bit depth through a small stack buffer and writes them to the peripheral.
|
||||
/// @param input Frames in the incoming stream format
|
||||
/// @param frames Number of frames to widen and write
|
||||
/// @return Bytes written, which is short of the widened size if a write timed out
|
||||
size_t write_widened_(const uint8_t *input, uint32_t frames);
|
||||
#endif
|
||||
|
||||
/// @brief Frame multiple for DMA buffers and writes; 2 keeps ESP32 mono sample pairs word-aligned.
|
||||
static uint32_t dma_frame_multiple(const audio::AudioStreamInfo &stream_info) {
|
||||
return esp32_mono_swap_needed(stream_info) ? 2 : 1;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
@@ -61,19 +60,6 @@ def validate_adc_time(value: Any) -> int:
|
||||
return cv.enum(ADC_TIMES, int=True)(value)
|
||||
|
||||
|
||||
def _per_channel(validator: Callable[[Any], Any]) -> Callable[[Any], ConfigType]:
|
||||
"""One value for both channels, or one each; always returns the per-channel dict."""
|
||||
return cv.Any(
|
||||
cv.All(validator, lambda value: {CONF_VOLTAGE: value, CONF_CURRENT: value}),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): validator,
|
||||
cv.Required(CONF_CURRENT): validator,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -108,8 +94,14 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(
|
||||
cv.current, cv.Range(min=0.0)
|
||||
),
|
||||
cv.Optional(CONF_ADC_TIME, default="1100 us"): _per_channel(
|
||||
validate_adc_time
|
||||
cv.Optional(CONF_ADC_TIME, default="1100 us"): cv.Any(
|
||||
validate_adc_time,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): validate_adc_time,
|
||||
cv.Required(CONF_CURRENT): validate_adc_time,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum(
|
||||
ADC_AVG_SAMPLES, int=True
|
||||
@@ -127,9 +119,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE]))
|
||||
cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT]))
|
||||
|
||||
adc_time = config[CONF_ADC_TIME]
|
||||
cg.add(var.set_adc_time_voltage(adc_time[CONF_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_current(adc_time[CONF_CURRENT]))
|
||||
adc_time_config = config[CONF_ADC_TIME]
|
||||
if isinstance(adc_time_config, dict):
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config[CONF_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_current(adc_time_config[CONF_CURRENT]))
|
||||
else:
|
||||
cg.add(var.set_adc_time_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_current(adc_time_config))
|
||||
|
||||
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
@@ -102,33 +101,19 @@ def validate_adc_time(value: Any) -> EnumValue:
|
||||
return cv.enum(ADC_TIMES, int=True)(value)
|
||||
|
||||
|
||||
def _per_channel(validator: Callable[[Any], Any]) -> Callable[[Any], ConfigType]:
|
||||
"""One value for every channel, or one each; always returns the per-channel dict."""
|
||||
return cv.Any(
|
||||
cv.All(
|
||||
validator,
|
||||
lambda value: {
|
||||
CONF_BUS_VOLTAGE: value,
|
||||
CONF_SHUNT_VOLTAGE: value,
|
||||
CONF_TEMPERATURE: value,
|
||||
},
|
||||
),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_BUS_VOLTAGE, default="4120 us"): validator,
|
||||
cv.Optional(CONF_SHUNT_VOLTAGE, default="4120 us"): validator,
|
||||
cv.Optional(CONF_TEMPERATURE, default="4120 us"): validator,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
INA2XX_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SHUNT_RESISTANCE): cv.All(cv.resistance, cv.Range(min=0.0)),
|
||||
cv.Required(CONF_MAX_CURRENT): cv.All(cv.current, cv.Range(min=0.0)),
|
||||
cv.Optional(CONF_ADC_RANGE, default=0): cv.int_range(min=0, max=1),
|
||||
cv.Optional(CONF_ADC_TIME, default="4120 us"): _per_channel(validate_adc_time),
|
||||
cv.Optional(CONF_ADC_TIME, default="4120 us"): cv.Any(
|
||||
validate_adc_time,
|
||||
{
|
||||
cv.Optional(CONF_BUS_VOLTAGE, default="4120 us"): validate_adc_time,
|
||||
cv.Optional(CONF_SHUNT_VOLTAGE, default="4120 us"): validate_adc_time,
|
||||
cv.Optional(CONF_TEMPERATURE, default="4120 us"): validate_adc_time,
|
||||
},
|
||||
),
|
||||
cv.Optional(CONF_ADC_AVERAGING, default=128): cv.enum(ADC_SAMPLES, int=True),
|
||||
cv.Optional(CONF_TEMPERATURE_COEFFICIENT, default=0): cv.int_range(
|
||||
min=0, max=16383
|
||||
@@ -229,10 +214,15 @@ async def setup_ina2xx(var: MockObj, config: ConfigType) -> None:
|
||||
cg.add(var.set_shunt_tempco(config[CONF_TEMPERATURE_COEFFICIENT]))
|
||||
cg.add(var.set_reset_on_boot(config[CONF_RESET_ON_BOOT]))
|
||||
|
||||
adc_time = config[CONF_ADC_TIME]
|
||||
cg.add(var.set_adc_time_bus_voltage(adc_time[CONF_BUS_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_shunt_voltage(adc_time[CONF_SHUNT_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_die_temperature(adc_time[CONF_TEMPERATURE]))
|
||||
adc_time_config = config[CONF_ADC_TIME]
|
||||
if isinstance(adc_time_config, dict):
|
||||
cg.add(var.set_adc_time_bus_voltage(adc_time_config[CONF_BUS_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_shunt_voltage(adc_time_config[CONF_SHUNT_VOLTAGE]))
|
||||
cg.add(var.set_adc_time_die_temperature(adc_time_config[CONF_TEMPERATURE]))
|
||||
else:
|
||||
cg.add(var.set_adc_time_bus_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_shunt_voltage(adc_time_config))
|
||||
cg.add(var.set_adc_time_die_temperature(adc_time_config))
|
||||
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_SHUNT_VOLTAGE, var.set_shunt_voltage_sensor)
|
||||
|
||||
@@ -34,10 +34,10 @@ enum OpMode : uint8_t {
|
||||
|
||||
class LD2420Listener {
|
||||
public:
|
||||
virtual void on_presence(bool presence) {};
|
||||
virtual void on_distance(uint16_t distance) {};
|
||||
virtual void on_energy(uint16_t *sensor_energy, size_t size) {};
|
||||
virtual void on_fw_version(std::string &fw) {};
|
||||
virtual void on_presence(bool presence){};
|
||||
virtual void on_distance(uint16_t distance){};
|
||||
virtual void on_energy(uint16_t *sensor_energy, size_t size){};
|
||||
virtual void on_fw_version(std::string &fw){};
|
||||
};
|
||||
|
||||
class LD2420Component final : public Component, public uart::UARTDevice {
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
#define SAFE_PUBLISH_SENSOR_UNKNOWN(sensor) (sensor).publish_state_unknown()
|
||||
|
||||
#define highbyte(val) (uint8_t) ((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t) ((val) & 0xff)
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
|
||||
namespace esphome::ld24xx {
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_LIBRETINY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -305,15 +304,6 @@ FRAMEWORK_SCHEMA = cv.All(
|
||||
|
||||
CONFIG_SCHEMA = cv.All(_notify_old_style, cv.require_platformio_toolchain("LibreTiny"))
|
||||
|
||||
|
||||
def _require_ipv4(config: dict) -> dict:
|
||||
"""Declares that LibreTiny builds need IPv4."""
|
||||
# Imported here: logger imports this module, so a module level import would load network for every config
|
||||
from esphome.components import network
|
||||
|
||||
return network.require_ipv4(CONF_LIBRETINY)(config)
|
||||
|
||||
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LTComponent),
|
||||
@@ -328,7 +318,6 @@ BASE_SCHEMA = cv.Schema(
|
||||
BASE_SCHEMA.add_extra(_detect_variant)
|
||||
BASE_SCHEMA.add_extra(cv.require_platformio_toolchain("LibreTiny"))
|
||||
BASE_SCHEMA.add_extra(_update_core_data)
|
||||
BASE_SCHEMA.add_extra(_require_ipv4)
|
||||
|
||||
|
||||
def _configure_lwip(config: dict) -> None:
|
||||
|
||||
@@ -14,10 +14,7 @@ template<bool HasTransitionLength, typename... Ts> class DimRelativeAction final
|
||||
|
||||
TEMPLATABLE_VALUE(float, relative_brightness)
|
||||
|
||||
template<typename V>
|
||||
void set_transition_length(V value)
|
||||
requires(HasTransitionLength)
|
||||
{
|
||||
template<typename V> void set_transition_length(V value) requires(HasTransitionLength) {
|
||||
this->transition_length_ = value;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user