Compare commits

..
Author SHA1 Message Date
J. Nick Koston ff36d3a6c8 Fetch the bh1745 hub once 2026-10-20 17:48:01 -10:00
J. Nick Koston ba3bc70bbb Merge branch 'apply-entities-output' into apply-entities-output-migrate 2026-10-20 17:36:24 -10:00
J. Nick Koston 6c1a7642f3 Merge remote-tracking branch 'origin/dev' into apply-entities-output 2026-10-20 17:36:24 -10:00
J. Nick Koston 8b0d1d31db [pca9685] Use Apply outputs for the channels 2026-10-20 17:35:42 -10:00
J. Nick Koston 653686e11e Pass the hub channel as the Apply output index
Every channel of a hub now shares one generated function. Adds
write_channel to tlc5947, tlc5971 and write_pwm_level to
waveshare_io_ch32v003 so apply strings hold no C++ math, drops the
now unused float_output.h and helpers.h includes, and fetches each
hub once.
2026-10-20 17:35:42 -10:00
J. Nick Koston d17dcaaee4 Merge branch 'apply-entities-output' into apply-entities-output-migrate 2026-10-20 17:29:36 -10:00
J. Nick Koston 1ce0db00b3 [output] Share one Apply output instantiation per hub with a channel index 2026-10-20 17:29:33 -10:00
J. Nick Koston 6409611e1c Drop the component schema from the Apply outputs 2026-10-20 17:23:25 -10:00
J. Nick Koston 86dcbe4645 [components] Use Apply outputs for hub channel outputs 2026-10-20 17:22:22 -10:00
J. Nick Koston a50a30414e [output] Add ApplyFloatOutput and ApplyBinaryOutput 2026-10-20 17:09:32 -10:00
456 changed files with 2954 additions and 5359 deletions
+3 -3
View File
@@ -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:
+1 -1
View File
@@ -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++]
+3 -6
View File
@@ -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:**
-1
View File
@@ -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
View File
@@ -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)
-4
View File
@@ -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()}"""
+8 -8
View File
@@ -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);
+23 -5
View File
@@ -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,
)
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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
}
+24 -13
View File
@@ -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);
+3 -3
View File
@@ -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};
+70 -70
View File
@@ -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
};
+1 -4
View File
@@ -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(); }
+6 -2
View File
@@ -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]) {
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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_; }
+4 -2
View File
@@ -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",
+1 -1
View File
@@ -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, &param);
}
#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
}
+5 -9
View File
@@ -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
+1 -1
View File
@@ -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; }
+12 -12
View File
@@ -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
+3 -3
View File
@@ -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];
+2 -1
View File
@@ -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;
+1 -18
View File
@@ -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);
+5 -9
View File
@@ -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)"
)
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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;
-18
View File
@@ -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)
+15 -5
View File
@@ -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) {
+24 -18
View File
@@ -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).
*
+22 -7
View File
@@ -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 {
+107 -27
View File
@@ -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
-1
View File
@@ -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"
+6 -13
View File
@@ -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
+2 -23
View File
@@ -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] = {
+8 -11
View File
@@ -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]))
+3 -3
View File
@@ -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;
+2 -5
View File
@@ -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 -2
View File
@@ -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,
)
-34
View File
@@ -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),
)
+1 -8
View File
@@ -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"
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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_);
-238
View File
@@ -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]))
-135
View File
@@ -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)
-48
View File
@@ -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
-2
View File
@@ -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) {
+2 -1
View File
@@ -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();
-3
View File
@@ -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
-10
View File
@@ -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, []),
+40 -11
View File
@@ -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;
}
}
}
}
+17 -14
View File
@@ -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};
};
+36 -11
View File
@@ -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
+8 -16
View File
@@ -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
-11
View File
@@ -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(
{
-2
View File
@@ -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 -6
View File
@@ -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
+2 -2
View File
@@ -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))
+1 -1
View File
@@ -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();
+3 -3
View File
@@ -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;
}
+1 -1
View File
@@ -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;
+1 -3
View File
@@ -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)
-2
View File
@@ -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),
)
+14 -14
View File
@@ -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;
+15 -19
View File
@@ -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]))
+17 -27
View File
@@ -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)
+4 -4
View File
@@ -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 {
+2 -2
View File
@@ -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 {
-11
View File
@@ -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:
+1 -4
View File
@@ -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