1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-04 09:01:49 +00:00

Merge branch 'dev' into fan_fixed

This commit is contained in:
J. Nick Koston
2025-10-28 22:15:50 -05:00
committed by GitHub
220 changed files with 2947 additions and 1128 deletions

View File

@@ -416,7 +416,7 @@ jobs:
} }
// Generate review messages // Generate review messages
function generateReviewMessages(finalLabels) { function generateReviewMessages(finalLabels, originalLabelCount) {
const messages = []; const messages = [];
const prAuthor = context.payload.pull_request.user.login; const prAuthor = context.payload.pull_request.user.login;
@@ -430,15 +430,15 @@ jobs:
.reduce((sum, file) => sum + (file.deletions || 0), 0); .reduce((sum, file) => sum + (file.deletions || 0), 0);
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions); const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
const tooManyLabels = finalLabels.length > MAX_LABELS; const tooManyLabels = originalLabelCount > MAX_LABELS;
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD; const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`; let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
if (tooManyLabels && tooManyChanges) { if (tooManyLabels && tooManyChanges) {
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${finalLabels.length} different components/areas.`; message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
} else if (tooManyLabels) { } else if (tooManyLabels) {
message += `This PR affects ${finalLabels.length} different components/areas.`; message += `This PR affects ${originalLabelCount} different components/areas.`;
} else { } else {
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`; message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
} }
@@ -466,8 +466,8 @@ jobs:
} }
// Handle reviews // Handle reviews
async function handleReviews(finalLabels) { async function handleReviews(finalLabels, originalLabelCount) {
const reviewMessages = generateReviewMessages(finalLabels); const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
const hasReviewableLabels = finalLabels.some(label => const hasReviewableLabels = finalLabels.some(label =>
['too-big', 'needs-codeowners'].includes(label) ['too-big', 'needs-codeowners'].includes(label)
); );
@@ -627,6 +627,7 @@ jobs:
// Handle too many labels (only for non-mega PRs) // Handle too many labels (only for non-mega PRs)
const tooManyLabels = finalLabels.length > MAX_LABELS; const tooManyLabels = finalLabels.length > MAX_LABELS;
const originalLabelCount = finalLabels.length;
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) { if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
finalLabels = ['too-big']; finalLabels = ['too-big'];
@@ -635,7 +636,7 @@ jobs:
console.log('Computed labels:', finalLabels.join(', ')); console.log('Computed labels:', finalLabels.join(', '));
// Handle reviews // Handle reviews
await handleReviews(finalLabels); await handleReviews(finalLabels, originalLabelCount);
// Apply labels // Apply labels
if (finalLabels.length > 0) { if (finalLabels.length > 0) {

View File

@@ -114,7 +114,7 @@ jobs:
matrix: matrix:
python-version: python-version:
- "3.11" - "3.11"
- "3.14" - "3.13"
os: os:
- ubuntu-latest - ubuntu-latest
- macOS-latest - macOS-latest
@@ -123,9 +123,9 @@ jobs:
# Minimize CI resource usage # Minimize CI resource usage
# by only running the Python version # by only running the Python version
# version used for docker images on Windows and macOS # version used for docker images on Windows and macOS
- python-version: "3.14" - python-version: "3.13"
os: windows-latest os: windows-latest
- python-version: "3.14" - python-version: "3.13"
os: macOS-latest os: macOS-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: needs:

View File

@@ -201,6 +201,7 @@ esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2 esphome/components/hbridge/switch/* @dwmw2
esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/he60r/* @clydebarrow esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal

View File

@@ -16,7 +16,12 @@ from esphome.const import (
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import ID from esphome.core import ID
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType from esphome.cpp_generator import (
LambdaExpression,
MockObj,
MockObjClass,
TemplateArgsType,
)
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType from esphome.types import ConfigType
from esphome.util import Registry from esphome.util import Registry
@@ -87,6 +92,7 @@ def validate_potentially_or_condition(value):
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
@@ -97,9 +103,40 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
def new_lambda_pvariable(
id_obj: ID,
lambda_expr: LambdaExpression,
stateless_class: MockObjClass,
template_arg: cg.TemplateArguments | None = None,
) -> MockObj:
"""Create Pvariable for lambda, using stateless class if applicable.
Combines ID selection and Pvariable creation in one call. For stateless
lambdas (empty capture), uses function pointer instead of std::function.
Args:
id_obj: The ID object (action_id, condition_id, or filter_id)
lambda_expr: The lambda expression object
stateless_class: The stateless class to use for stateless lambdas
template_arg: Optional template arguments (for actions/conditions)
Returns:
The created Pvariable
"""
# For stateless lambdas, use function pointer instead of std::function
if lambda_expr.capture == "":
id_obj = id_obj.copy()
id_obj.type = stateless_class
if template_arg is not None:
return cg.new_Pvariable(id_obj, template_arg, lambda_expr)
return cg.new_Pvariable(id_obj, lambda_expr)
def validate_automation(extra_schema=None, extra_validators=None, single=False): def validate_automation(extra_schema=None, extra_validators=None, single=False):
if extra_schema is None: if extra_schema is None:
extra_schema = {} extra_schema = {}
@@ -240,7 +277,9 @@ async def lambda_condition_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
return cg.new_Pvariable(condition_id, template_arg, lambda_) return new_lambda_pvariable(
condition_id, lambda_, StatelessLambdaCondition, template_arg
)
@register_condition( @register_condition(
@@ -406,7 +445,7 @@ async def lambda_action_to_code(
args: TemplateArgsType, args: TemplateArgsType,
) -> MockObj: ) -> MockObj:
lambda_ = await cg.process_lambda(config, args, return_type=cg.void) lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg)
@register_action( @register_action(

View File

@@ -62,6 +62,7 @@ from esphome.cpp_types import ( # noqa: F401
EntityBase, EntityBase,
EntityCategory, EntityCategory,
ESPTime, ESPTime,
FixedVector,
GPIOPin, GPIOPin,
InternalGPIOPin, InternalGPIOPin,
JsonObject, JsonObject,

View File

@@ -9,7 +9,7 @@ static const char *const TAG = "adalight_light_effect";
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000; static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000; static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {} AdalightLightEffect::AdalightLightEffect(const char *name) : AddressableLightEffect(name) {}
void AdalightLightEffect::start() { void AdalightLightEffect::start() {
AddressableLightEffect::start(); AddressableLightEffect::start();

View File

@@ -11,7 +11,7 @@ namespace adalight {
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice { class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
public: public:
AdalightLightEffect(const std::string &name); AdalightLightEffect(const char *name);
void start() override; void start() override;
void stop() override; void stop() override;

View File

@@ -71,10 +71,12 @@ SERVICE_ARG_NATIVE_TYPES = {
"int": cg.int32, "int": cg.int32,
"float": float, "float": float,
"string": cg.std_string, "string": cg.std_string,
"bool[]": cg.std_vector.template(bool), "bool[]": cg.FixedVector.template(bool).operator("const").operator("ref"),
"int[]": cg.std_vector.template(cg.int32), "int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
"float[]": cg.std_vector.template(float), "float[]": cg.FixedVector.template(float).operator("const").operator("ref"),
"string[]": cg.std_vector.template(cg.std_string), "string[]": cg.FixedVector.template(cg.std_string)
.operator("const")
.operator("ref"),
} }
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay" CONF_BATCH_DELAY = "batch_delay"
@@ -258,6 +260,10 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES") cg.add_define("USE_API_SERVICES")
# Set USE_API_CUSTOM_SERVICES if external components need dynamic service registration
if config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_CUSTOM_SERVICES")
if config[CONF_HOMEASSISTANT_SERVICES]: if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES") cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
@@ -265,6 +271,8 @@ async def to_code(config):
cg.add_define("USE_API_HOMEASSISTANT_STATES") cg.add_define("USE_API_HOMEASSISTANT_STATES")
if actions := config.get(CONF_ACTIONS, []): if actions := config.get(CONF_ACTIONS, []):
# Collect all triggers first, then register all at once with initializer_list
triggers: list[cg.Pvariable] = []
for conf in actions: for conf in actions:
template_args = [] template_args = []
func_args = [] func_args = []
@@ -278,8 +286,10 @@ async def to_code(config):
trigger = cg.new_Pvariable( trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
) )
cg.add(var.register_user_service(trigger)) triggers.append(trigger)
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
# Register all services at once - single allocation, no reallocations
cg.add(var.initialize_user_services(triggers))
if CONF_ON_CLIENT_CONNECTED in config: if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER") cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")

View File

@@ -989,7 +989,7 @@ message ListEntitiesClimateResponse {
bool supports_current_temperature = 5; // Deprecated: use feature_flags bool supports_current_temperature = 5; // Deprecated: use feature_flags
bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags bool supports_two_point_target_temperature = 6; // Deprecated: use feature_flags
repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"]; repeated ClimateMode supported_modes = 7 [(container_pointer_no_template) = "climate::ClimateModeMask"];
float visual_min_temperature = 8; float visual_min_temperature = 8;
float visual_max_temperature = 9; float visual_max_temperature = 9;
float visual_target_temperature_step = 10; float visual_target_temperature_step = 10;
@@ -998,11 +998,11 @@ message ListEntitiesClimateResponse {
// Deprecated in API version 1.5 // Deprecated in API version 1.5
bool legacy_supports_away = 11 [deprecated=true]; bool legacy_supports_away = 11 [deprecated=true];
bool supports_action = 12; // Deprecated: use feature_flags bool supports_action = 12; // Deprecated: use feature_flags
repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"]; repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer_no_template) = "climate::ClimateFanModeMask"];
repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"]; repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer_no_template) = "climate::ClimateSwingModeMask"];
repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"]; repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::vector"];
repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"]; repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"];
repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"]; repeated string supported_custom_presets = 17 [(container_pointer) = "std::vector"];
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 20; EntityCategory entity_category = 20;
@@ -1143,7 +1143,7 @@ message ListEntitiesSelectResponse {
reserved 4; // Deprecated: was string unique_id reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
repeated string options = 6 [(container_pointer) = "std::vector"]; repeated string options = 6 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];

View File

@@ -486,7 +486,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
if (light->supports_effects()) { if (light->supports_effects()) {
msg.effects.emplace_back("None"); msg.effects.emplace_back("None");
for (auto *effect : light->get_effects()) { for (auto *effect : light->get_effects()) {
msg.effects.push_back(effect->get_name()); msg.effects.emplace_back(effect->get_name());
} }
} }
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
@@ -669,18 +669,18 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION); msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
// Current feature flags and other supported parameters // Current feature flags and other supported parameters
msg.feature_flags = traits.get_feature_flags(); msg.feature_flags = traits.get_feature_flags();
msg.supported_modes = &traits.get_supported_modes_for_api_(); msg.supported_modes = &traits.get_supported_modes();
msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step(); msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
msg.visual_min_humidity = traits.get_visual_min_humidity(); msg.visual_min_humidity = traits.get_visual_min_humidity();
msg.visual_max_humidity = traits.get_visual_max_humidity(); msg.visual_max_humidity = traits.get_visual_max_humidity();
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_(); msg.supported_fan_modes = &traits.get_supported_fan_modes();
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_(); msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
msg.supported_presets = &traits.get_supported_presets_for_api_(); msg.supported_presets = &traits.get_supported_presets();
msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_(); msg.supported_custom_presets = &traits.get_supported_custom_presets();
msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_(); msg.supported_swing_modes = &traits.get_supported_swing_modes();
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }

View File

@@ -1475,8 +1475,8 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_ENTITY_ICON #ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon_ref_); buffer.encode_string(5, this->icon_ref_);
#endif #endif
for (const auto &it : *this->options) { for (const char *it : *this->options) {
buffer.encode_string(6, it, true); buffer.encode_string(6, it, strlen(it), true);
} }
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category)); buffer.encode_uint32(8, static_cast<uint32_t>(this->entity_category));
@@ -1492,8 +1492,8 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size()); size.add_length(1, this->icon_ref_.size());
#endif #endif
if (!this->options->empty()) { if (!this->options->empty()) {
for (const auto &it : *this->options) { for (const char *it : *this->options) {
size.add_length_force(1, it.size()); size.add_length_force(1, strlen(it));
} }
} }
size.add_bool(1, this->disabled_by_default); size.add_bool(1, this->disabled_by_default);

View File

@@ -1377,16 +1377,16 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
#endif #endif
bool supports_current_temperature{false}; bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false}; bool supports_two_point_target_temperature{false};
const std::set<climate::ClimateMode> *supported_modes{}; const climate::ClimateModeMask *supported_modes{};
float visual_min_temperature{0.0f}; float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f}; float visual_max_temperature{0.0f};
float visual_target_temperature_step{0.0f}; float visual_target_temperature_step{0.0f};
bool supports_action{false}; bool supports_action{false};
const std::set<climate::ClimateFanMode> *supported_fan_modes{}; const climate::ClimateFanModeMask *supported_fan_modes{};
const std::set<climate::ClimateSwingMode> *supported_swing_modes{}; const climate::ClimateSwingModeMask *supported_swing_modes{};
const std::set<std::string> *supported_custom_fan_modes{}; const std::vector<std::string> *supported_custom_fan_modes{};
const std::set<climate::ClimatePreset> *supported_presets{}; const climate::ClimatePresetMask *supported_presets{};
const std::set<std::string> *supported_custom_presets{}; const std::vector<std::string> *supported_custom_presets{};
float visual_current_temperature_step{0.0f}; float visual_current_temperature_step{0.0f};
bool supports_current_humidity{false}; bool supports_current_humidity{false};
bool supports_target_humidity{false}; bool supports_target_humidity{false};
@@ -1534,7 +1534,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_select_response"; } const char *message_name() const override { return "list_entities_select_response"; }
#endif #endif
const std::vector<std::string> *options{}; const FixedVector<const char *> *options{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -88,6 +88,12 @@ static void dump_field(std::string &out, const char *field_name, StringRef value
out.append("\n"); out.append("\n");
} }
static void dump_field(std::string &out, const char *field_name, const char *value, int indent = 2) {
append_field_prefix(out, field_name, indent);
out.append("'").append(value).append("'");
out.append("\n");
}
template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) { template<typename T> static void dump_field(std::string &out, const char *field_name, T value, int indent = 2) {
append_field_prefix(out, field_name, indent); append_field_prefix(out, field_name, indent);
out.append(proto_enum_to_string<T>(value)); out.append(proto_enum_to_string<T>(value));

View File

@@ -125,8 +125,14 @@ class APIServer : public Component, public Controller {
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES #endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
this->user_services_.assign(services);
}
#ifdef USE_API_CUSTOM_SERVICES
// Only compile push_back method when custom_services: true (external components)
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif #endif
#endif
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
#endif #endif

View File

@@ -53,8 +53,14 @@ class CustomAPIDevice {
template<typename T, typename... Ts> template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name, void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) { const std::array<std::string, sizeof...(Ts)> &arg_names) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
} }
#else #else
template<typename T, typename... Ts> template<typename T, typename... Ts>
@@ -86,8 +92,14 @@ class CustomAPIDevice {
*/ */
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) { template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
#ifdef USE_API_CUSTOM_SERVICES
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
#else
static_assert(
sizeof(T) == 0,
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
#endif
} }
#else #else
template<typename T> void register_service(void (T::*callback)(), const std::string &name) { template<typename T> void register_service(void (T::*callback)(), const std::string &name) {

View File

@@ -11,23 +11,58 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
} }
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; } template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; } template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) { template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
return std::vector<bool>(arg.bool_array.begin(), arg.bool_array.end()); std::vector<bool> result;
result.reserve(arg.bool_array.size());
result.insert(result.end(), arg.bool_array.begin(), arg.bool_array.end());
return result;
} }
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) { template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
return std::vector<int32_t>(arg.int_array.begin(), arg.int_array.end()); std::vector<int32_t> result;
result.reserve(arg.int_array.size());
result.insert(result.end(), arg.int_array.begin(), arg.int_array.end());
return result;
} }
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) { template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
return std::vector<float>(arg.float_array.begin(), arg.float_array.end()); std::vector<float> result;
result.reserve(arg.float_array.size());
result.insert(result.end(), arg.float_array.begin(), arg.float_array.end());
return result;
} }
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) { template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
return std::vector<std::string>(arg.string_array.begin(), arg.string_array.end()); std::vector<std::string> result;
result.reserve(arg.string_array.size());
result.insert(result.end(), arg.string_array.begin(), arg.string_array.end());
return result;
}
// New FixedVector const reference versions for YAML-generated services - zero-copy
template<>
const FixedVector<bool> &get_execute_arg_value<const FixedVector<bool> &>(const ExecuteServiceArgument &arg) {
return arg.bool_array;
}
template<>
const FixedVector<int32_t> &get_execute_arg_value<const FixedVector<int32_t> &>(const ExecuteServiceArgument &arg) {
return arg.int_array;
}
template<>
const FixedVector<float> &get_execute_arg_value<const FixedVector<float> &>(const ExecuteServiceArgument &arg) {
return arg.float_array;
}
template<>
const FixedVector<std::string> &get_execute_arg_value<const FixedVector<std::string> &>(
const ExecuteServiceArgument &arg) {
return arg.string_array;
} }
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; } template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; } template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; } template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; } template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
// Legacy std::vector versions for external components using custom_api_device.h
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; } template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() { template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY; return enums::SERVICE_ARG_TYPE_INT_ARRAY;
@@ -39,4 +74,18 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
return enums::SERVICE_ARG_TYPE_STRING_ARRAY; return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
} }
// New FixedVector const reference versions for YAML-generated services
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<bool> &>() {
return enums::SERVICE_ARG_TYPE_BOOL_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<int32_t> &>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<float> &>() {
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<const FixedVector<std::string> &>() {
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
}
} // namespace esphome::api } // namespace esphome::api

View File

@@ -99,9 +99,8 @@ enum BedjetCommand : uint8_t {
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; static constexpr const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
} // namespace bedjet } // namespace bedjet
} // namespace esphome } // namespace esphome

View File

@@ -43,7 +43,7 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
}); });
// It would be better if we had a slider for the fan modes. // It would be better if we had a slider for the fan modes.
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET); traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
traits.set_supported_presets({ traits.set_supported_presets({
// If we support NONE, then have to decide what happens if the user switches to it (turn off?) // If we support NONE, then have to decide what happens if the user switches to it (turn off?)
// climate::CLIMATE_PRESET_NONE, // climate::CLIMATE_PRESET_NONE,

View File

@@ -155,6 +155,7 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter)
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
_LOGGER = getLogger(__name__) _LOGGER = getLogger(__name__)
@@ -299,7 +300,7 @@ async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool) config, [(bool, "x")], return_type=cg.optional.template(bool)
) )
return cg.new_Pvariable(filter_id, lambda_) return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter)
@register_filter( @register_filter(

View File

@@ -111,6 +111,21 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_; std::function<optional<bool>(bool)> f_;
}; };
/** Optimized lambda filter for stateless lambdas (no capture).
*
* Uses function pointer instead of std::function to reduce memory overhead.
* Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function).
*/
class StatelessLambdaFilter : public Filter {
public:
explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {}
optional<bool> new_value(bool value) override { return this->f_(value); }
protected:
optional<bool> (*f_)(bool);
};
class SettleFilter : public Filter, public Component { class SettleFilter : public Filter, public Component {
public: public:
optional<bool> new_value(bool value) override; optional<bool> new_value(bool value) override;

View File

@@ -96,8 +96,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
BLEClientWriteAction(BLEClient *ble_client) { BLEClientWriteAction(BLEClient *ble_client) {
ble_client->register_ble_node(this); ble_client->register_ble_node(this);
ble_client_ = ble_client; ble_client_ = ble_client;
this->construct_simple_value_();
} }
~BLEClientWriteAction() { this->destroy_simple_value_(); }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
@@ -106,14 +109,18 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) { void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
this->value_template_ = std::move(func); this->destroy_simple_value_();
has_simple_value_ = false; this->value_.template_func = func;
this->has_simple_value_ = false;
} }
void set_value_simple(const std::vector<uint8_t> &value) { void set_value_simple(const std::vector<uint8_t> &value) {
this->value_simple_ = value; if (!this->has_simple_value_) {
has_simple_value_ = true; this->construct_simple_value_();
}
this->value_.simple = value;
this->has_simple_value_ = true;
} }
void play(Ts... x) override {} void play(Ts... x) override {}
@@ -121,7 +128,7 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void play_complex(Ts... x) override { void play_complex(Ts... x) override {
this->num_running_++; this->num_running_++;
this->var_ = std::make_tuple(x...); this->var_ = std::make_tuple(x...);
auto value = this->has_simple_value_ ? this->value_simple_ : this->value_template_(x...); auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work. // on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!write(value)) if (!write(value))
this->play_next_(x...); this->play_next_(x...);
@@ -194,10 +201,22 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
} }
private: private:
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
void destroy_simple_value_() {
if (this->has_simple_value_) {
this->value_.simple.~vector();
}
}
BLEClient *ble_client_; BLEClient *ble_client_;
bool has_simple_value_ = true; bool has_simple_value_ = true;
std::vector<uint8_t> value_simple_; union Value {
std::function<std::vector<uint8_t>(Ts...)> value_template_{}; std::vector<uint8_t> simple;
std::vector<uint8_t> (*template_func)(Ts...);
Value() {} // trivial constructor
~Value() {} // trivial destructor - we manage lifetime via discriminator
} value_;
espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_; espbt::ESPBTUUID char_uuid_;
std::tuple<Ts...> var_{}; std::tuple<Ts...> var_{};
@@ -213,9 +232,9 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
void play(Ts... x) override { void play(Ts... x) override {
uint32_t passkey; uint32_t passkey;
if (has_simple_value_) { if (has_simple_value_) {
passkey = this->value_simple_; passkey = this->value_.simple;
} else { } else {
passkey = this->value_template_(x...); passkey = this->value_.template_func(x...);
} }
if (passkey > 999999) if (passkey > 999999)
return; return;
@@ -224,21 +243,23 @@ template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...
esp_ble_passkey_reply(remote_bda, true, passkey); esp_ble_passkey_reply(remote_bda, true, passkey);
} }
void set_value_template(std::function<uint32_t(Ts...)> func) { void set_value_template(uint32_t (*func)(Ts...)) {
this->value_template_ = std::move(func); this->value_.template_func = func;
has_simple_value_ = false; this->has_simple_value_ = false;
} }
void set_value_simple(const uint32_t &value) { void set_value_simple(const uint32_t &value) {
this->value_simple_ = value; this->value_.simple = value;
has_simple_value_ = true; this->has_simple_value_ = true;
} }
private: private:
BLEClient *parent_{nullptr}; BLEClient *parent_{nullptr};
bool has_simple_value_ = true; bool has_simple_value_ = true;
uint32_t value_simple_{0}; union {
std::function<uint32_t(Ts...)> value_template_{}; uint32_t simple;
uint32_t (*template_func)(Ts...);
} value_{.simple = 0};
}; };
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> { template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
@@ -249,27 +270,29 @@ template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Ac
esp_bd_addr_t remote_bda; esp_bd_addr_t remote_bda;
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
if (has_simple_value_) { if (has_simple_value_) {
esp_ble_confirm_reply(remote_bda, this->value_simple_); esp_ble_confirm_reply(remote_bda, this->value_.simple);
} else { } else {
esp_ble_confirm_reply(remote_bda, this->value_template_(x...)); esp_ble_confirm_reply(remote_bda, this->value_.template_func(x...));
} }
} }
void set_value_template(std::function<bool(Ts...)> func) { void set_value_template(bool (*func)(Ts...)) {
this->value_template_ = std::move(func); this->value_.template_func = func;
has_simple_value_ = false; this->has_simple_value_ = false;
} }
void set_value_simple(const bool &value) { void set_value_simple(const bool &value) {
this->value_simple_ = value; this->value_.simple = value;
has_simple_value_ = true; this->has_simple_value_ = true;
} }
private: private:
BLEClient *parent_{nullptr}; BLEClient *parent_{nullptr};
bool has_simple_value_ = true; bool has_simple_value_ = true;
bool value_simple_{false}; union {
std::function<bool(Ts...)> value_template_{}; bool simple;
bool (*template_func)(Ts...);
} value_{.simple = false};
}; };
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> { template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {

View File

@@ -117,9 +117,9 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
} }
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) { float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
if (this->data_to_value_func_.has_value()) { if (this->has_data_to_value_) {
std::vector<uint8_t> data(value, value + value_len); std::vector<uint8_t> data(value, value + value_len);
return (*this->data_to_value_func_)(data); return this->data_to_value_func_(data);
} else { } else {
return value[0]; return value[0];
} }

View File

@@ -15,8 +15,6 @@ namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker; namespace espbt = esphome::esp32_ble_tracker;
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode { class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
public: public:
void loop() override; void loop() override;
@@ -33,13 +31,17 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; } void set_data_to_value(float (*lambda)(const std::vector<uint8_t> &)) {
this->data_to_value_func_ = lambda;
this->has_data_to_value_ = true;
}
void set_enable_notify(bool notify) { this->notify_ = notify; } void set_enable_notify(bool notify) { this->notify_ = notify; }
uint16_t handle; uint16_t handle;
protected: protected:
float parse_data_(uint8_t *value, uint16_t value_len); float parse_data_(uint8_t *value, uint16_t value_len);
optional<data_to_value_t> data_to_value_func_{}; bool has_data_to_value_{false};
float (*data_to_value_func_)(const std::vector<uint8_t> &){};
bool notify_; bool notify_;
espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_; espbt::ESPBTUUID char_uuid_;

View File

@@ -385,7 +385,7 @@ void Climate::save_state_() {
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
state.uses_custom_fan_mode = true; state.uses_custom_fan_mode = true;
const auto &supported = traits.get_supported_custom_fan_modes(); const auto &supported = traits.get_supported_custom_fan_modes();
// std::set has consistent order (lexicographic for strings) // std::vector maintains insertion order
size_t i = 0; size_t i = 0;
for (const auto &mode : supported) { for (const auto &mode : supported) {
if (mode == custom_fan_mode) { if (mode == custom_fan_mode) {
@@ -402,7 +402,7 @@ void Climate::save_state_() {
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
state.uses_custom_preset = true; state.uses_custom_preset = true;
const auto &supported = traits.get_supported_custom_presets(); const auto &supported = traits.get_supported_custom_presets();
// std::set has consistent order (lexicographic for strings) // std::vector maintains insertion order
size_t i = 0; size_t i = 0;
for (const auto &preset : supported) { for (const auto &preset : supported) {
if (preset == custom_preset) { if (preset == custom_preset) {

View File

@@ -7,6 +7,7 @@ namespace esphome {
namespace climate { namespace climate {
/// Enum for all modes a climate device can be in. /// Enum for all modes a climate device can be in.
/// NOTE: If adding values, update ClimateModeMask in climate_traits.h to use the new last value
enum ClimateMode : uint8_t { enum ClimateMode : uint8_t {
/// The climate device is off /// The climate device is off
CLIMATE_MODE_OFF = 0, CLIMATE_MODE_OFF = 0,
@@ -24,7 +25,7 @@ enum ClimateMode : uint8_t {
* For example, the target temperature can be adjusted based on a schedule, or learned behavior. * For example, the target temperature can be adjusted based on a schedule, or learned behavior.
* The target temperature can't be adjusted when in this mode. * The target temperature can't be adjusted when in this mode.
*/ */
CLIMATE_MODE_AUTO = 6 CLIMATE_MODE_AUTO = 6 // Update ClimateModeMask in climate_traits.h if adding values after this
}; };
/// Enum for the current action of the climate device. Values match those of ClimateMode. /// Enum for the current action of the climate device. Values match those of ClimateMode.
@@ -43,6 +44,7 @@ enum ClimateAction : uint8_t {
CLIMATE_ACTION_FAN = 6, CLIMATE_ACTION_FAN = 6,
}; };
/// NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value
enum ClimateFanMode : uint8_t { enum ClimateFanMode : uint8_t {
/// The fan mode is set to On /// The fan mode is set to On
CLIMATE_FAN_ON = 0, CLIMATE_FAN_ON = 0,
@@ -63,10 +65,11 @@ enum ClimateFanMode : uint8_t {
/// The fan mode is set to Diffuse /// The fan mode is set to Diffuse
CLIMATE_FAN_DIFFUSE = 8, CLIMATE_FAN_DIFFUSE = 8,
/// The fan mode is set to Quiet /// The fan mode is set to Quiet
CLIMATE_FAN_QUIET = 9, CLIMATE_FAN_QUIET = 9, // Update ClimateFanModeMask in climate_traits.h if adding values after this
}; };
/// Enum for all modes a climate swing can be in /// Enum for all modes a climate swing can be in
/// NOTE: If adding values, update ClimateSwingModeMask in climate_traits.h to use the new last value
enum ClimateSwingMode : uint8_t { enum ClimateSwingMode : uint8_t {
/// The swing mode is set to Off /// The swing mode is set to Off
CLIMATE_SWING_OFF = 0, CLIMATE_SWING_OFF = 0,
@@ -75,10 +78,11 @@ enum ClimateSwingMode : uint8_t {
/// The fan mode is set to Vertical /// The fan mode is set to Vertical
CLIMATE_SWING_VERTICAL = 2, CLIMATE_SWING_VERTICAL = 2,
/// The fan mode is set to Horizontal /// The fan mode is set to Horizontal
CLIMATE_SWING_HORIZONTAL = 3, CLIMATE_SWING_HORIZONTAL = 3, // Update ClimateSwingModeMask in climate_traits.h if adding values after this
}; };
/// Enum for all preset modes /// Enum for all preset modes
/// NOTE: If adding values, update ClimatePresetMask in climate_traits.h to use the new last value
enum ClimatePreset : uint8_t { enum ClimatePreset : uint8_t {
/// No preset is active /// No preset is active
CLIMATE_PRESET_NONE = 0, CLIMATE_PRESET_NONE = 0,
@@ -95,7 +99,7 @@ enum ClimatePreset : uint8_t {
/// Device is prepared for sleep /// Device is prepared for sleep
CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_SLEEP = 6,
/// Device is reacting to activity (e.g., movement sensors) /// Device is reacting to activity (e.g., movement sensors)
CLIMATE_PRESET_ACTIVITY = 7, CLIMATE_PRESET_ACTIVITY = 7, // Update ClimatePresetMask in climate_traits.h if adding values after this
}; };
enum ClimateFeature : uint32_t { enum ClimateFeature : uint32_t {

View File

@@ -1,19 +1,33 @@
#pragma once #pragma once
#include <set> #include <vector>
#include "climate_mode.h" #include "climate_mode.h"
#include "esphome/core/finite_set_mask.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
#ifdef USE_API
namespace api {
class APIConnection;
} // namespace api
#endif
namespace climate { namespace climate {
// Type aliases for climate enum bitmasks
// These replace std::set<EnumType> to eliminate red-black tree overhead
// For contiguous enums starting at 0, DefaultBitPolicy provides 1:1 mapping (enum value = bit position)
// Bitmask size is automatically calculated from the last enum value
using ClimateModeMask = FiniteSetMask<ClimateMode, DefaultBitPolicy<ClimateMode, CLIMATE_MODE_AUTO + 1>>;
using ClimateFanModeMask = FiniteSetMask<ClimateFanMode, DefaultBitPolicy<ClimateFanMode, CLIMATE_FAN_QUIET + 1>>;
using ClimateSwingModeMask =
FiniteSetMask<ClimateSwingMode, DefaultBitPolicy<ClimateSwingMode, CLIMATE_SWING_HORIZONTAL + 1>>;
using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimatePreset, CLIMATE_PRESET_ACTIVITY + 1>>;
// Lightweight linear search for small vectors (1-20 items)
// Avoids std::find template overhead
template<typename T> inline bool vector_contains(const std::vector<T> &vec, const T &value) {
for (const auto &item : vec) {
if (item == value)
return true;
}
return false;
}
/** This class contains all static data for climate devices. /** This class contains all static data for climate devices.
* *
* All climate devices must support these features: * All climate devices must support these features:
@@ -107,48 +121,60 @@ class ClimateTraits {
} }
} }
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); } void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); } void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); } bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; } const ClimateModeMask &get_supported_modes() const { return this->supported_modes_; }
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); } void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; }
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); } void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); } void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.push_back(mode); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { bool get_supports_fan_modes() const {
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty(); return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
} }
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; } const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) { void set_supported_custom_fan_modes(std::vector<std::string> supported_custom_fan_modes) {
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
} }
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) {
this->supported_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(modes, modes + N);
}
const std::vector<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
return this->supported_custom_fan_modes_.count(custom_fan_mode); return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
} }
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); } void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); } void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); } void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.push_back(preset); }
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); } bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
bool get_supports_presets() const { return !this->supported_presets_.empty(); } bool get_supports_presets() const { return !this->supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; } const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) { void set_supported_custom_presets(std::vector<std::string> supported_custom_presets) {
this->supported_custom_presets_ = std::move(supported_custom_presets); this->supported_custom_presets_ = std::move(supported_custom_presets);
} }
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; } void set_supported_custom_presets(std::initializer_list<std::string> presets) {
this->supported_custom_presets_ = presets;
}
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
this->supported_custom_presets_.assign(presets, presets + N);
}
const std::vector<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
bool supports_custom_preset(const std::string &custom_preset) const { bool supports_custom_preset(const std::string &custom_preset) const {
return this->supported_custom_presets_.count(custom_preset); return vector_contains(this->supported_custom_presets_, custom_preset);
} }
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); } void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); } void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); } bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); } bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; } const ClimateSwingModeMask &get_supported_swing_modes() const { return this->supported_swing_modes_; }
float get_visual_min_temperature() const { return this->visual_min_temperature_; } float get_visual_min_temperature() const { return this->visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { void set_visual_min_temperature(float visual_min_temperature) {
@@ -179,23 +205,6 @@ class ClimateTraits {
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
protected: protected:
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// These methods return references to internal data structures.
// They are used by the API to avoid copying data when encoding messages.
// Warning: Do not use these methods outside of the API connection code.
// They return references to internal data that can be invalidated.
const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; }
const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; }
const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const {
return this->supported_custom_fan_modes_;
}
const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; }
const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; }
const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; }
#endif
void set_mode_support_(climate::ClimateMode mode, bool supported) { void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) { if (supported) {
this->supported_modes_.insert(mode); this->supported_modes_.insert(mode);
@@ -226,12 +235,12 @@ class ClimateTraits {
float visual_min_humidity_{30}; float visual_min_humidity_{30};
float visual_max_humidity_{99}; float visual_max_humidity_{99};
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF}; climate::ClimateModeMask supported_modes_{climate::CLIMATE_MODE_OFF};
std::set<climate::ClimateFanMode> supported_fan_modes_; climate::ClimateFanModeMask supported_fan_modes_;
std::set<climate::ClimateSwingMode> supported_swing_modes_; climate::ClimateSwingModeMask supported_swing_modes_;
std::set<climate::ClimatePreset> supported_presets_; climate::ClimatePresetMask supported_presets_;
std::set<std::string> supported_custom_fan_modes_; std::vector<std::string> supported_custom_fan_modes_;
std::set<std::string> supported_custom_presets_; std::vector<std::string> supported_custom_presets_;
}; };
} // namespace climate } // namespace climate

View File

@@ -24,16 +24,18 @@ class ClimateIR : public Component,
public remote_base::RemoteTransmittable { public remote_base::RemoteTransmittable {
public: public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {}, bool supports_dry = false, bool supports_fan_only = false,
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) { climate::ClimateFanModeMask fan_modes = climate::ClimateFanModeMask(),
climate::ClimateSwingModeMask swing_modes = climate::ClimateSwingModeMask(),
climate::ClimatePresetMask presets = climate::ClimatePresetMask()) {
this->minimum_temperature_ = minimum_temperature; this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature; this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step; this->temperature_step_ = temperature_step;
this->supports_dry_ = supports_dry; this->supports_dry_ = supports_dry;
this->supports_fan_only_ = supports_fan_only; this->supports_fan_only_ = supports_fan_only;
this->fan_modes_ = std::move(fan_modes); this->fan_modes_ = fan_modes;
this->swing_modes_ = std::move(swing_modes); this->swing_modes_ = swing_modes;
this->presets_ = std::move(presets); this->presets_ = presets;
} }
void setup() override; void setup() override;
@@ -60,9 +62,9 @@ class ClimateIR : public Component,
bool supports_heat_{true}; bool supports_heat_{true};
bool supports_dry_{false}; bool supports_dry_{false};
bool supports_fan_only_{false}; bool supports_fan_only_{false};
std::set<climate::ClimateFanMode> fan_modes_ = {}; climate::ClimateFanModeMask fan_modes_{};
std::set<climate::ClimateSwingMode> swing_modes_ = {}; climate::ClimateSwingModeMask swing_modes_{};
std::set<climate::ClimatePreset> presets_ = {}; climate::ClimatePresetMask presets_{};
sensor::Sensor *sensor_{nullptr}; sensor::Sensor *sensor_{nullptr};
}; };

View File

@@ -80,8 +80,8 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
return; return;
} }
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name().c_str(), ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_first_universe(), light_effect->get_last_universe()); light_effect->get_last_universe());
light_effects_.insert(light_effect); light_effects_.insert(light_effect);
@@ -95,8 +95,8 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
return; return;
} }
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name().c_str(), ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_first_universe(), light_effect->get_last_universe()); light_effect->get_last_universe());
light_effects_.erase(light_effect); light_effects_.erase(light_effect);

View File

@@ -9,7 +9,7 @@ namespace e131 {
static const char *const TAG = "e131_addressable_light_effect"; static const char *const TAG = "e131_addressable_light_effect";
static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1); static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1);
E131AddressableLightEffect::E131AddressableLightEffect(const std::string &name) : AddressableLightEffect(name) {} E131AddressableLightEffect::E131AddressableLightEffect(const char *name) : AddressableLightEffect(name) {}
int E131AddressableLightEffect::get_data_per_universe() const { return get_lights_per_universe() * channels_; } int E131AddressableLightEffect::get_data_per_universe() const { return get_lights_per_universe() * channels_; }
@@ -58,8 +58,8 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
auto *input_data = packet.values + 1; auto *input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name().c_str(), universe, ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
output_offset, output_end); output_end);
switch (channels_) { switch (channels_) {
case E131_MONO: case E131_MONO:

View File

@@ -13,7 +13,7 @@ enum E131LightChannels { E131_MONO = 1, E131_RGB = 3, E131_RGBW = 4 };
class E131AddressableLightEffect : public light::AddressableLightEffect { class E131AddressableLightEffect : public light::AddressableLightEffect {
public: public:
E131AddressableLightEffect(const std::string &name); E131AddressableLightEffect(const char *name);
void start() override; void start() override;
void stop() override; void stop() override;

View File

@@ -304,9 +304,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str: def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to # format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
# a PIO platformio/framework-espidf value # a PIO platformio/framework-espidf value
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
ext = "tar.xz"
else:
ext = "zip"
if release: if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
def _is_framework_url(source: str) -> str: def _is_framework_url(source: str) -> str:
@@ -355,6 +359,7 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
ESP_IDF_PLATFORM_VERSION_LOOKUP = { ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"), cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"), cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"), cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"), cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"), cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),

View File

@@ -40,13 +40,13 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
// - 3 bytes for members below // - 3 bytes for members below
// - 1 byte padding for alignment // - 1 byte padding for alignment
// - 4 bytes for vtable pointer // - 4 bytes for vtable pointer
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32) uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
gpio::Flags flags_; // GPIO flags (1 byte) gpio::Flags flags_{}; // GPIO flags (1 byte)
struct PinFlags { struct PinFlags {
uint8_t inverted : 1; // Invert pin logic (1 bit) uint8_t inverted : 1; // Invert pin logic (1 bit)
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits) uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
uint8_t reserved : 5; // Reserved for future use (5 bits) uint8_t reserved : 5; // Reserved for future use (5 bits)
} pin_flags_; // Total: 1 byte } pin_flags_{}; // Total: 1 byte
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static bool isr_service_installed; static bool isr_service_installed;
}; };

View File

@@ -223,7 +223,10 @@ async def esp32_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
cg.add(var.set_inverted(config[CONF_INVERTED])) # Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
if CONF_DRIVE_STRENGTH in config: if CONF_DRIVE_STRENGTH in config:
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH])) cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))

View File

@@ -461,7 +461,9 @@ async def parse_value(value_config, args):
if isinstance(value, str): if isinstance(value, str):
value = list(value.encode(value_config[CONF_STRING_ENCODING])) value = list(value.encode(value_config[CONF_STRING_ENCODING]))
if isinstance(value, list): if isinstance(value, list):
return cg.std_vector.template(cg.uint8)(value) # Generate initializer list {1, 2, 3} instead of std::vector<uint8_t>({1, 2, 3})
# This calls the set_value(std::initializer_list<uint8_t>) overload
return cg.ArrayInitializer(*value)
val = cg.RawExpression(f"{value_config[CONF_TYPE]}({cg.safe_exp(value)})") val = cg.RawExpression(f"{value_config[CONF_TYPE]}({cg.safe_exp(value)})")
return ByteBuffer_ns.wrap(val, value_config[CONF_ENDIANNESS]) return ByteBuffer_ns.wrap(val, value_config[CONF_ENDIANNESS])

View File

@@ -35,13 +35,18 @@ BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties)
void BLECharacteristic::set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); } void BLECharacteristic::set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
void BLECharacteristic::set_value(const std::vector<uint8_t> &buffer) { void BLECharacteristic::set_value(std::vector<uint8_t> &&buffer) {
xSemaphoreTake(this->set_value_lock_, 0L); xSemaphoreTake(this->set_value_lock_, 0L);
this->value_ = buffer; this->value_ = std::move(buffer);
xSemaphoreGive(this->set_value_lock_); xSemaphoreGive(this->set_value_lock_);
} }
void BLECharacteristic::set_value(std::initializer_list<uint8_t> data) {
this->set_value(std::vector<uint8_t>(data)); // Delegate to move overload
}
void BLECharacteristic::set_value(const std::string &buffer) { void BLECharacteristic::set_value(const std::string &buffer) {
this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); this->set_value(std::vector<uint8_t>(buffer.begin(), buffer.end())); // Delegate to move overload
} }
void BLECharacteristic::notify() { void BLECharacteristic::notify() {

View File

@@ -33,7 +33,8 @@ class BLECharacteristic {
~BLECharacteristic(); ~BLECharacteristic();
void set_value(ByteBuffer buffer); void set_value(ByteBuffer buffer);
void set_value(const std::vector<uint8_t> &buffer); void set_value(std::vector<uint8_t> &&buffer);
void set_value(std::initializer_list<uint8_t> data);
void set_value(const std::string &buffer); void set_value(const std::string &buffer);
void set_broadcast_property(bool value); void set_broadcast_property(bool value);

View File

@@ -46,15 +46,17 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
this->state_ = CREATING; this->state_ = CREATING;
} }
void BLEDescriptor::set_value(std::vector<uint8_t> buffer) { void BLEDescriptor::set_value(std::vector<uint8_t> &&buffer) { this->set_value_impl_(buffer.data(), buffer.size()); }
size_t length = buffer.size();
void BLEDescriptor::set_value(std::initializer_list<uint8_t> data) { this->set_value_impl_(data.begin(), data.size()); }
void BLEDescriptor::set_value_impl_(const uint8_t *data, size_t length) {
if (length > this->value_.attr_max_len) { if (length > this->value_.attr_max_len) {
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", length, this->value_.attr_max_len); ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", length, this->value_.attr_max_len);
return; return;
} }
this->value_.attr_len = length; this->value_.attr_len = length;
memcpy(this->value_.attr_value, buffer.data(), length); memcpy(this->value_.attr_value, data, length);
} }
void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,

View File

@@ -27,7 +27,8 @@ class BLEDescriptor {
void do_create(BLECharacteristic *characteristic); void do_create(BLECharacteristic *characteristic);
ESPBTUUID get_uuid() const { return this->uuid_; } ESPBTUUID get_uuid() const { return this->uuid_; }
void set_value(std::vector<uint8_t> buffer); void set_value(std::vector<uint8_t> &&buffer);
void set_value(std::initializer_list<uint8_t> data);
void set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); } void set_value(ByteBuffer buffer) { this->set_value(buffer.get_data()); }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@@ -42,6 +43,8 @@ class BLEDescriptor {
} }
protected: protected:
void set_value_impl_(const uint8_t *data, size_t length);
BLECharacteristic *characteristic_{nullptr}; BLECharacteristic *characteristic_{nullptr};
ESPBTUUID uuid_; ESPBTUUID uuid_;
uint16_t handle_{0xFFFF}; uint16_t handle_{0xFFFF};

View File

@@ -270,8 +270,8 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
} }
} }
void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) { void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &&response) {
this->rpc_response_->set_value(ByteBuffer::wrap(response)); this->rpc_response_->set_value(std::move(response));
if (this->state_ != improv::STATE_STOPPED) if (this->state_ != improv::STATE_STOPPED)
this->rpc_response_->notify(); this->rpc_response_->notify();
} }
@@ -409,10 +409,8 @@ void ESP32ImprovComponent::check_wifi_connection_() {
} }
} }
#endif #endif
// Pass to build_rpc_response using vector constructor from iterators to avoid extra copies this->send_response_(improv::build_rpc_response(improv::WIFI_SETTINGS,
std::vector<uint8_t> data = improv::build_rpc_response( std::vector<std::string>(url_strings, url_strings + url_count)));
improv::WIFI_SETTINGS, std::vector<std::string>(url_strings, url_strings + url_count));
this->send_response_(data);
} else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) { } else if (this->is_active() && this->state_ != improv::STATE_PROVISIONED) {
ESP_LOGD(TAG, "WiFi provisioned externally"); ESP_LOGD(TAG, "WiFi provisioned externally");
} }

View File

@@ -109,7 +109,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
void set_state_(improv::State state, bool update_advertising = true); void set_state_(improv::State state, bool update_advertising = true);
void set_error_(improv::Error error); void set_error_(improv::Error error);
improv::State get_initial_state_() const; improv::State get_initial_state_() const;
void send_response_(std::vector<uint8_t> &response); void send_response_(std::vector<uint8_t> &&response);
void process_incoming_data_(); void process_incoming_data_();
void on_wifi_connect_timeout_(); void on_wifi_connect_timeout_();
void check_wifi_connection_(); void check_wifi_connection_();

View File

@@ -29,8 +29,8 @@ class ESP8266GPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_; uint8_t pin_;
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace esp8266 } // namespace esp8266

View File

@@ -165,7 +165,10 @@ async def esp8266_pin_to_code(config):
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
mode = config[CONF_MODE] mode = config[CONF_MODE]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(mode))) cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
if num < 16: if num < 16:
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][ initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][

View File

@@ -14,7 +14,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
from esphome.components.network import IPAddress from esphome.components.network import ip_address_literal
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -32,6 +32,7 @@ from esphome.const import (
CONF_MISO_PIN, CONF_MISO_PIN,
CONF_MODE, CONF_MODE,
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_NUMBER,
CONF_PAGE_ID, CONF_PAGE_ID,
CONF_PIN, CONF_PIN,
CONF_POLLING_INTERVAL, CONF_POLLING_INTERVAL,
@@ -52,12 +53,36 @@ from esphome.core import (
coroutine_with_priority, coroutine_with_priority,
) )
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.types import ConfigType
CONFLICTS_WITH = ["wifi"] CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["network"] AUTO_LOAD = ["network"]
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
# RMII pins that are hardcoded on ESP32 classic and cannot be changed
# These pins are used by the internal Ethernet MAC when using RMII PHYs
ESP32_RMII_FIXED_PINS = {
19: "EMAC_TXD0",
21: "EMAC_TX_EN",
22: "EMAC_TXD1",
25: "EMAC_RXD0",
26: "EMAC_RXD1",
27: "EMAC_RX_CRS_DV",
}
# RMII default pins for ESP32-P4
# These are the default pins used by ESP-IDF and are configurable in principle,
# but ESPHome's ethernet component currently has no way to change them
ESP32P4_RMII_DEFAULT_PINS = {
34: "EMAC_TXD0",
35: "EMAC_TXD1",
28: "EMAC_RX_CRS_DV",
29: "EMAC_RXD0",
30: "EMAC_RXD1",
49: "EMAC_TX_EN",
}
ethernet_ns = cg.esphome_ns.namespace("ethernet") ethernet_ns = cg.esphome_ns.namespace("ethernet")
PHYRegister = ethernet_ns.struct("PHYRegister") PHYRegister = ethernet_ns.struct("PHYRegister")
CONF_PHY_ADDR = "phy_addr" CONF_PHY_ADDR = "phy_addr"
@@ -273,7 +298,7 @@ CONFIG_SCHEMA = cv.All(
) )
def _final_validate(config): def _final_validate_spi(config):
if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: if config[CONF_TYPE] not in SPI_ETHERNET_TYPES:
return return
if spi_configs := fv.full_config.get().get(CONF_SPI): if spi_configs := fv.full_config.get().get(CONF_SPI):
@@ -292,17 +317,14 @@ def _final_validate(config):
) )
FINAL_VALIDATE_SCHEMA = _final_validate
def manual_ip(config): def manual_ip(config):
return cg.StructInitializer( return cg.StructInitializer(
ManualIP, ManualIP,
("static_ip", IPAddress(str(config[CONF_STATIC_IP]))), ("static_ip", ip_address_literal(config[CONF_STATIC_IP])),
("gateway", IPAddress(str(config[CONF_GATEWAY]))), ("gateway", ip_address_literal(config[CONF_GATEWAY])),
("subnet", IPAddress(str(config[CONF_SUBNET]))), ("subnet", ip_address_literal(config[CONF_SUBNET])),
("dns1", IPAddress(str(config[CONF_DNS1]))), ("dns1", ip_address_literal(config[CONF_DNS1])),
("dns2", IPAddress(str(config[CONF_DNS2]))), ("dns2", ip_address_literal(config[CONF_DNS2])),
) )
@@ -383,3 +405,57 @@ async def to_code(config):
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
def _final_validate_rmii_pins(config: ConfigType) -> None:
"""Validate that RMII pins are not used by other components."""
# Only validate for RMII-based PHYs on ESP32/ESP32P4
if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH":
return # SPI and OPENETH don't use RMII
variant = get_esp32_variant()
if variant == VARIANT_ESP32:
rmii_pins = ESP32_RMII_FIXED_PINS
is_configurable = False
elif variant == VARIANT_ESP32P4:
rmii_pins = ESP32P4_RMII_DEFAULT_PINS
is_configurable = True
else:
return # No RMII validation needed for other variants
# Check all used pins against RMII reserved pins
for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values():
for pin_path, _, pin_config in pin_list:
pin_num = pin_config.get(CONF_NUMBER)
if pin_num not in rmii_pins:
continue
# Found a conflict - show helpful error message
pin_function = rmii_pins[pin_num]
component_path = ".".join(str(p) for p in pin_path)
if is_configurable:
error_msg = (
f"GPIO{pin_num} is used by Ethernet RMII "
f"({pin_function}) with the current default "
f"configuration. This conflicts with '{component_path}'. "
f"Please choose a different GPIO pin for "
f"'{component_path}'."
)
else:
error_msg = (
f"GPIO{pin_num} is reserved for Ethernet RMII "
f"({pin_function}) and cannot be used. This pin is "
f"hardcoded by ESP-IDF and cannot be changed when using "
f"RMII Ethernet PHYs. Please choose a different GPIO pin "
f"for '{component_path}'."
)
raise cv.Invalid(error_msg, path=pin_path)
def _final_validate(config: ConfigType) -> ConfigType:
"""Final validation for Ethernet component."""
_final_validate_spi(config)
_final_validate_rmii_pins(config)
return config
FINAL_VALIDATE_SCHEMA = _final_validate

View File

@@ -60,8 +60,6 @@ class FanCall {
this->speed_ = speed; this->speed_ = speed;
return *this; return *this;
} }
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
FanCall &set_speed(const char *legacy_speed);
optional<int> get_speed() const { return this->speed_; } optional<int> get_speed() const { return this->speed_; }
FanCall &set_direction(FanDirection direction) { FanCall &set_direction(FanDirection direction) {
this->direction_ = direction; this->direction_ = direction;

View File

@@ -171,7 +171,7 @@ void HaierClimateBase::toggle_power() {
PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()}); PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()});
} }
void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { void HaierClimateBase::set_supported_swing_modes(climate::ClimateSwingModeMask modes) {
this->traits_.set_supported_swing_modes(modes); this->traits_.set_supported_swing_modes(modes);
if (!modes.empty()) if (!modes.empty())
this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
@@ -179,13 +179,13 @@ void HaierClimateBase::set_supported_swing_modes(const std::set<climate::Climate
void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); } void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); }
void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { void HaierClimateBase::set_supported_modes(climate::ClimateModeMask modes) {
this->traits_.set_supported_modes(modes); this->traits_.set_supported_modes(modes);
this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available
this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); // Always available this->traits_.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); // Always available
} }
void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) { void HaierClimateBase::set_supported_presets(climate::ClimatePresetMask presets) {
this->traits_.set_supported_presets(presets); this->traits_.set_supported_presets(presets);
if (!presets.empty()) if (!presets.empty())
this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE); this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE);

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <set>
#include "esphome/components/climate/climate.h" #include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
@@ -60,9 +59,9 @@ class HaierClimateBase : public esphome::Component,
void send_power_off_command(); void send_power_off_command();
void toggle_power(); void toggle_power();
void reset_protocol() { this->reset_protocol_request_ = true; }; void reset_protocol() { this->reset_protocol_request_ = true; };
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes); void set_supported_modes(esphome::climate::ClimateModeMask modes);
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes); void set_supported_swing_modes(esphome::climate::ClimateSwingModeMask modes);
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); void set_supported_presets(esphome::climate::ClimatePresetMask presets);
bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
size_t read_array(uint8_t *data, size_t len) noexcept override { size_t read_array(uint8_t *data, size_t len) noexcept override {

View File

@@ -1033,9 +1033,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
{ {
// Swing mode // Swing mode
ClimateSwingMode old_swing_mode = this->swing_mode; ClimateSwingMode old_swing_mode = this->swing_mode;
const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes(); const auto &swing_modes = traits_.get_supported_swing_modes();
bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end(); bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL);
bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end(); bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL);
if (horizontal_swing_supported && if (horizontal_swing_supported &&
(packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) { (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
if (vertical_swing_supported && if (vertical_swing_supported &&
@@ -1218,13 +1218,13 @@ void HonClimate::fill_control_messages_queue_() {
(uint8_t) hon_protocol::DataParameters::QUIET_MODE, (uint8_t) hon_protocol::DataParameters::QUIET_MODE,
quiet_mode_buf, 2); quiet_mode_buf, 2);
} }
if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) {
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::FAST_MODE, (uint8_t) hon_protocol::DataParameters::FAST_MODE,
fast_mode_buf, 2); fast_mode_buf, 2);
} }
if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) {
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::TEN_DEGREE, (uint8_t) hon_protocol::DataParameters::TEN_DEGREE,

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@optimusprimespace", "@ssieb"]

View File

@@ -0,0 +1,111 @@
#include "esphome/core/hal.h"
#include "hdc2010.h"
// https://github.com/vigsterkr/homebridge-hdc2010/blob/main/src/hdc2010.js
// https://github.com/lime-labs/HDC2080-Arduino/blob/master/src/HDC2080.cpp
namespace esphome {
namespace hdc2010 {
static const char *const TAG = "hdc2010";
static const uint8_t HDC2010_ADDRESS = 0x40; // 0b1000000 or 0b1000001 from datasheet
static const uint8_t HDC2010_CMD_CONFIGURATION_MEASUREMENT = 0x8F;
static const uint8_t HDC2010_CMD_START_MEASUREMENT = 0xF9;
static const uint8_t HDC2010_CMD_TEMPERATURE_LOW = 0x00;
static const uint8_t HDC2010_CMD_TEMPERATURE_HIGH = 0x01;
static const uint8_t HDC2010_CMD_HUMIDITY_LOW = 0x02;
static const uint8_t HDC2010_CMD_HUMIDITY_HIGH = 0x03;
static const uint8_t CONFIG = 0x0E;
static const uint8_t MEASUREMENT_CONFIG = 0x0F;
void HDC2010Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
const uint8_t data[2] = {
0b00000000, // resolution 14bit for both humidity and temperature
0b00000000 // reserved
};
if (!this->write_bytes(HDC2010_CMD_CONFIGURATION_MEASUREMENT, data, 2)) {
ESP_LOGW(TAG, "Initial config instruction error");
this->status_set_warning();
return;
}
// Set measurement mode to temperature and humidity
uint8_t config_contents;
this->read_register(MEASUREMENT_CONFIG, &config_contents, 1);
config_contents = (config_contents & 0xF9); // Always set to TEMP_AND_HUMID mode
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
// Set rate to manual
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0x8F;
this->write_bytes(CONFIG, &config_contents, 1);
// Set temperature resolution to 14bit
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0x3F;
this->write_bytes(CONFIG, &config_contents, 1);
// Set humidity resolution to 14bit
this->read_register(CONFIG, &config_contents, 1);
config_contents &= 0xCF;
this->write_bytes(CONFIG, &config_contents, 1);
}
void HDC2010Component::dump_config() {
ESP_LOGCONFIG(TAG, "HDC2010:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
void HDC2010Component::update() {
// Trigger measurement
uint8_t config_contents;
this->read_register(CONFIG, &config_contents, 1);
config_contents |= 0x01;
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
// 1ms delay after triggering the sample
set_timeout(1, [this]() {
if (this->temperature_sensor_ != nullptr) {
float temp = this->read_temp();
this->temperature_sensor_->publish_state(temp);
ESP_LOGD(TAG, "Temp=%.1f°C", temp);
}
if (this->humidity_sensor_ != nullptr) {
float humidity = this->read_humidity();
this->humidity_sensor_->publish_state(humidity);
ESP_LOGD(TAG, "Humidity=%.1f%%", humidity);
}
});
}
float HDC2010Component::read_temp() {
uint8_t byte[2];
this->read_register(HDC2010_CMD_TEMPERATURE_LOW, &byte[0], 1);
this->read_register(HDC2010_CMD_TEMPERATURE_HIGH, &byte[1], 1);
uint16_t temp = encode_uint16(byte[1], byte[0]);
return (float) temp * 0.0025177f - 40.0f;
}
float HDC2010Component::read_humidity() {
uint8_t byte[2];
this->read_register(HDC2010_CMD_HUMIDITY_LOW, &byte[0], 1);
this->read_register(HDC2010_CMD_HUMIDITY_HIGH, &byte[1], 1);
uint16_t humidity = encode_uint16(byte[1], byte[0]);
return (float) humidity * 0.001525879f;
}
} // namespace hdc2010
} // namespace esphome

View File

@@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace hdc2010 {
class HDC2010Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
/// Setup the sensor and check for connection.
void setup() override;
void dump_config() override;
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
void update() override;
float read_temp();
float read_humidity();
protected:
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace hdc2010
} // namespace esphome

View File

@@ -0,0 +1,56 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
DEPENDENCIES = ["i2c"]
hdc2010_ns = cg.esphome_ns.namespace("hdc2010")
HDC2010Component = hdc2010_ns.class_(
"HDC2010Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HDC2010Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens))

View File

@@ -97,12 +97,11 @@ const float TEMP_MAX = 100; // Celsius
class HeatpumpIRClimate : public climate_ir::ClimateIR { class HeatpumpIRClimate : public climate_ir::ClimateIR {
public: public:
HeatpumpIRClimate() HeatpumpIRClimate()
: climate_ir::ClimateIR( : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
TEMP_MIN, TEMP_MAX, 1.0f, true, true, {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
std::set<climate::ClimateFanMode>{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_AUTO},
climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_AUTO}, {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL,
std::set<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {}
climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {}
void setup() override; void setup() override;
void set_protocol(Protocol protocol) { this->protocol_ = protocol; } void set_protocol(Protocol protocol) { this->protocol_ = protocol; }
void set_horizontal_default(HorizontalDirection horizontal_direction) { void set_horizontal_default(HorizontalDirection horizontal_direction) {

View File

@@ -28,8 +28,8 @@ class HostGPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_; uint8_t pin_;
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace host } // namespace host

View File

@@ -57,6 +57,9 @@ async def host_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

@@ -12,7 +12,6 @@ from esphome.const import (
CONF_ON_ERROR, CONF_ON_ERROR,
CONF_ON_RESPONSE, CONF_ON_RESPONSE,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_URL, CONF_URL,
CONF_WATCHDOG_TIMEOUT, CONF_WATCHDOG_TIMEOUT,
PLATFORM_HOST, PLATFORM_HOST,
@@ -216,16 +215,8 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
f"{CONF_VERIFY_SSL} has moved to the base component configuration." f"{CONF_VERIFY_SSL} has moved to the base component configuration."
), ),
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean, cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation( cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(single=True),
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)} cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
),
cv.Optional(CONF_ON_ERROR): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
automation.Trigger.template()
)
}
),
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes, cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
} }
) )
@@ -280,7 +271,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string) template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_)) cg.add(var.set_url(template_))
cg.add(var.set_method(config[CONF_METHOD])) cg.add(var.set_method(config[CONF_METHOD]))
cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
capture_response = config[CONF_CAPTURE_RESPONSE]
if capture_response:
cg.add(var.set_capture_response(capture_response))
cg.add_define("USE_HTTP_REQUEST_RESPONSE")
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE])) cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
if CONF_BODY in config: if CONF_BODY in config:
@@ -303,21 +299,26 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
for value in config.get(CONF_COLLECT_HEADERS, []): for value in config.get(CONF_COLLECT_HEADERS, []):
cg.add(var.add_collect_header(value)) cg.add(var.add_collect_header(value))
for conf in config.get(CONF_ON_RESPONSE, []): if response_conf := config.get(CONF_ON_RESPONSE):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) if capture_response:
cg.add(var.register_response_trigger(trigger)) await automation.build_automation(
await automation.build_automation( var.get_success_trigger_with_response(),
trigger, [
[ (cg.std_shared_ptr.template(HttpContainer), "response"),
(cg.std_shared_ptr.template(HttpContainer), "response"), (cg.std_string_ref, "body"),
(cg.std_string_ref, "body"), *args,
], ],
conf, response_conf,
) )
for conf in config.get(CONF_ON_ERROR, []): else:
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) await automation.build_automation(
cg.add(var.register_error_trigger(trigger)) var.get_success_trigger(),
await automation.build_automation(trigger, [], conf) [(cg.std_shared_ptr.template(HttpContainer), "response"), *args],
response_conf,
)
if error_conf := config.get(CONF_ON_ERROR):
await automation.build_automation(var.get_error_trigger(), args, error_conf)
return var return var

View File

@@ -124,7 +124,7 @@ class HttpRequestComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void set_useragent(const char *useragent) { this->useragent_ = useragent; } void set_useragent(const char *useragent) { this->useragent_ = useragent; }
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; } void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; } uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; } void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
@@ -169,11 +169,11 @@ class HttpRequestComponent : public Component {
protected: protected:
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
const std::string &body, const std::list<Header> &request_headers, const std::string &body, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) = 0; const std::set<std::string> &collect_headers) = 0;
const char *useragent_{nullptr}; const char *useragent_{nullptr};
bool follow_redirects_{}; bool follow_redirects_{};
uint16_t redirect_limit_{}; uint16_t redirect_limit_{};
uint16_t timeout_{4500}; uint32_t timeout_{4500};
uint32_t watchdog_timeout_{0}; uint32_t watchdog_timeout_{0};
}; };
@@ -183,7 +183,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
TEMPLATABLE_VALUE(std::string, url) TEMPLATABLE_VALUE(std::string, url)
TEMPLATABLE_VALUE(const char *, method) TEMPLATABLE_VALUE(const char *, method)
TEMPLATABLE_VALUE(std::string, body) TEMPLATABLE_VALUE(std::string, body)
#ifdef USE_HTTP_REQUEST_RESPONSE
TEMPLATABLE_VALUE(bool, capture_response) TEMPLATABLE_VALUE(bool, capture_response)
#endif
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) { void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
this->request_headers_.insert({key, value}); this->request_headers_.insert({key, value});
@@ -195,9 +197,14 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; } void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); } #ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() const {
return this->success_trigger_with_response_;
}
#endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() const { return this->success_trigger_; }
void register_error_trigger(Trigger<> *trigger) { this->error_triggers_.push_back(trigger); } Trigger<Ts...> *get_error_trigger() const { return this->error_trigger_; }
void set_max_response_buffer_size(size_t max_response_buffer_size) { void set_max_response_buffer_size(size_t max_response_buffer_size) {
this->max_response_buffer_size_ = max_response_buffer_size; this->max_response_buffer_size_ = max_response_buffer_size;
@@ -228,17 +235,20 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers, auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
this->collect_headers_); this->collect_headers_);
auto captured_args = std::make_tuple(x...);
if (container == nullptr) { if (container == nullptr) {
for (auto *trigger : this->error_triggers_) std::apply([this](Ts... captured_args_inner) { this->error_trigger_->trigger(captured_args_inner...); },
trigger->trigger(); captured_args);
return; return;
} }
size_t content_length = container->content_length; size_t content_length = container->content_length;
size_t max_length = std::min(content_length, this->max_response_buffer_size_); size_t max_length = std::min(content_length, this->max_response_buffer_size_);
std::string response_body; #ifdef USE_HTTP_REQUEST_RESPONSE
if (this->capture_response_.value(x...)) { if (this->capture_response_.value(x...)) {
std::string response_body;
RAMAllocator<uint8_t> allocator; RAMAllocator<uint8_t> allocator;
uint8_t *buf = allocator.allocate(max_length); uint8_t *buf = allocator.allocate(max_length);
if (buf != nullptr) { if (buf != nullptr) {
@@ -253,19 +263,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
response_body.assign((char *) buf, read_index); response_body.assign((char *) buf, read_index);
allocator.deallocate(buf, max_length); allocator.deallocate(buf, max_length);
} }
} std::apply(
[this, &container, &response_body](Ts... captured_args_inner) {
if (this->response_triggers_.size() == 1) { this->success_trigger_with_response_->trigger(container, response_body, captured_args_inner...);
// if there is only one trigger, no need to copy the response body },
this->response_triggers_[0]->process(container, response_body); captured_args);
} else { } else
for (auto *trigger : this->response_triggers_) { #endif
// with multiple triggers, pass a copy of the response body to each {
// one so that modifications made in one trigger are not visible to std::apply([this, &container](
// the others Ts... captured_args_inner) { this->success_trigger_->trigger(container, captured_args_inner...); },
auto response_body_copy = std::string(response_body); captured_args);
trigger->process(container, response_body_copy);
}
} }
container->end(); container->end();
} }
@@ -283,8 +291,13 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
std::set<std::string> collect_headers_{"content-type", "content-length"}; std::set<std::string> collect_headers_{"content-type", "content-length"};
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{}; std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject)> json_func_{nullptr}; std::function<void(Ts..., JsonObject)> json_func_{nullptr};
std::vector<HttpRequestResponseTrigger *> response_triggers_{}; #ifdef USE_HTTP_REQUEST_RESPONSE
std::vector<Trigger<> *> error_triggers_{}; Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *success_trigger_with_response_ =
new Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...>();
#endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *success_trigger_ =
new Trigger<std::shared_ptr<HttpContainer>, Ts...>();
Trigger<Ts...> *error_trigger_ = new Trigger<Ts...>();
size_t max_response_buffer_size_{SIZE_MAX}; size_t max_response_buffer_size_{SIZE_MAX};
}; };

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "http_request.arduino";
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method, std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
const std::string &body, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) { const std::set<std::string> &collect_headers) {
if (!network::is_connected()) { if (!network::is_connected()) {
this->status_momentary_error("failed", 1000); this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -33,7 +33,7 @@ class HttpRequestArduino : public HttpRequestComponent {
protected: protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body, std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) override; const std::set<std::string> &collect_headers) override;
}; };
} // namespace http_request } // namespace http_request

View File

@@ -20,7 +20,7 @@ static const char *const TAG = "http_request.host";
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method, std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
const std::string &body, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> response_headers) { const std::set<std::string> &response_headers) {
if (!network::is_connected()) { if (!network::is_connected()) {
this->status_momentary_error("failed", 1000); this->status_momentary_error("failed", 1000);
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
public: public:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body, std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> response_headers) override; const std::set<std::string> &response_headers) override;
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; } void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
protected: protected:

View File

@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method, std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
const std::string &body, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) { const std::set<std::string> &collect_headers) {
if (!network::is_connected()) { if (!network::is_connected()) {
this->status_momentary_error("failed", 1000); this->status_momentary_error("failed", 1000);
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network"); ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");

View File

@@ -39,7 +39,7 @@ class HttpRequestIDF : public HttpRequestComponent {
protected: protected:
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body, std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
const std::list<Header> &request_headers, const std::list<Header> &request_headers,
std::set<std::string> collect_headers) override; const std::set<std::string> &collect_headers) override;
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE // if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{}; uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{}; uint16_t buffer_size_tx_{};

View File

@@ -28,6 +28,38 @@ void ImprovSerialComponent::setup() {
} }
} }
void ImprovSerialComponent::loop() {
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
ESP_LOGV(TAG, "Timeout");
}
auto byte = this->read_byte_();
while (byte.has_value()) {
if (this->parse_improv_serial_byte_(byte.value())) {
this->last_read_byte_ = millis();
} else {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
}
byte = this->read_byte_();
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); } void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
optional<uint8_t> ImprovSerialComponent::read_byte_() { optional<uint8_t> ImprovSerialComponent::read_byte_() {
@@ -78,8 +110,28 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
return byte; return byte;
} }
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) { void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) {
data.push_back('\n'); // First, set length field
this->tx_header_[TX_LENGTH_IDX] = this->tx_header_[TX_TYPE_IDX] == TYPE_RPC_RESPONSE ? size : 1;
const bool there_is_data = data != nullptr && size > 0;
// If there_is_data, checksum must not include our optional data byte
const uint8_t header_checksum_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE - 2;
// Only transmit the full buffer length if there is no data (only state/error byte is provided in this case)
const uint8_t header_tx_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE;
// Calculate checksum for message
uint8_t checksum = 0;
for (uint8_t i = 0; i < header_checksum_len; i++) {
checksum += this->tx_header_[i];
}
if (there_is_data) {
// Include data in checksum
for (size_t i = 0; i < size; i++) {
checksum += data[i];
}
}
this->tx_header_[TX_CHECKSUM_IDX] = checksum;
#ifdef USE_ESP32 #ifdef USE_ESP32
switch (logger::global_logger->get_uart()) { switch (logger::global_logger->get_uart()) {
case logger::UART_SELECTION_UART0: case logger::UART_SELECTION_UART0:
@@ -87,63 +139,45 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_UART2: case logger::UART_SELECTION_UART2:
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #endif
uart_write_bytes(this->uart_num_, data.data(), data.size()); uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len);
if (there_is_data) {
uart_write_bytes(this->uart_num_, data, size);
uart_write_bytes(this->uart_num_, &this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
}
break; break;
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC) #if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
case logger::UART_SELECTION_USB_CDC: { case logger::UART_SELECTION_USB_CDC:
const char *msg = (char *) data.data(); esp_usb_console_write_buf((const char *) this->tx_header_, header_tx_len);
esp_usb_console_write_buf(msg, data.size()); if (there_is_data) {
esp_usb_console_write_buf((const char *) data, size);
esp_usb_console_write_buf((const char *) &this->tx_header_[TX_CHECKSUM_IDX],
2); // Footer: checksum and newline
}
break; break;
} #endif
#endif // USE_LOGGER_USB_CDC
#ifdef USE_LOGGER_USB_SERIAL_JTAG #ifdef USE_LOGGER_USB_SERIAL_JTAG
case logger::UART_SELECTION_USB_SERIAL_JTAG: case logger::UART_SELECTION_USB_SERIAL_JTAG:
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS); usb_serial_jtag_write_bytes((const char *) this->tx_header_, header_tx_len, 20 / portTICK_PERIOD_MS);
delay(10); if (there_is_data) {
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7 usb_serial_jtag_write_bytes((const char *) data, size, 20 / portTICK_PERIOD_MS);
usb_serial_jtag_write_bytes((const char *) &this->tx_header_[TX_CHECKSUM_IDX], 2,
20 / portTICK_PERIOD_MS); // Footer: checksum and newline
}
break; break;
#endif // USE_LOGGER_USB_SERIAL_JTAG #endif
default: default:
break; break;
} }
#elif defined(USE_ARDUINO) #elif defined(USE_ARDUINO)
this->hw_serial_->write(data.data(), data.size()); this->hw_serial_->write(this->tx_header_, header_tx_len);
if (there_is_data) {
this->hw_serial_->write(data, size);
this->hw_serial_->write(&this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
}
#endif #endif
} }
void ImprovSerialComponent::loop() {
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
ESP_LOGV(TAG, "Improv Serial timeout");
}
auto byte = this->read_byte_();
while (byte.has_value()) {
if (this->parse_improv_serial_byte_(byte.value())) {
this->last_read_byte_ = millis();
} else {
this->last_read_byte_ = 0;
this->rx_buffer_.clear();
}
byte = this->read_byte_();
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::vector<std::string> urls; std::vector<std::string> urls;
#ifdef USE_IMPROV_SERIAL_NEXT_URL #ifdef USE_IMPROV_SERIAL_NEXT_URL
@@ -177,13 +211,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size(); size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte); this->rx_buffer_.push_back(byte);
ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte); ESP_LOGV(TAG, "Byte: 0x%02X", byte);
const uint8_t *raw = &this->rx_buffer_[0]; const uint8_t *raw = &this->rx_buffer_[0];
return improv::parse_improv_serial_byte( return improv::parse_improv_serial_byte(
at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); }, at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
[this](improv::Error error) -> void { [this](improv::Error error) -> void {
ESP_LOGW(TAG, "Error decoding Improv payload"); ESP_LOGW(TAG, "Error decoding payload");
this->set_error_(error); this->set_error_(error);
}); });
} }
@@ -199,7 +233,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
wifi::global_wifi_component->set_sta(sta); wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_connecting(sta, false); wifi::global_wifi_component->start_connecting(sta, false);
this->set_state_(improv::STATE_PROVISIONING); this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str()); command.password.c_str());
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this); auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
@@ -240,7 +274,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
return true; return true;
} }
default: { default: {
ESP_LOGW(TAG, "Unknown Improv payload"); ESP_LOGW(TAG, "Unknown payload");
this->set_error_(improv::ERROR_UNKNOWN_RPC); this->set_error_(improv::ERROR_UNKNOWN_RPC);
return false; return false;
} }
@@ -249,57 +283,26 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
void ImprovSerialComponent::set_state_(improv::State state) { void ImprovSerialComponent::set_state_(improv::State state) {
this->state_ = state; this->state_ = state;
this->tx_header_[TX_TYPE_IDX] = TYPE_CURRENT_STATE;
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'}; this->tx_header_[TX_DATA_IDX] = state;
data.resize(11); this->write_data_();
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
} }
void ImprovSerialComponent::set_error_(improv::Error error) { void ImprovSerialComponent::set_error_(improv::Error error) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'}; this->tx_header_[TX_TYPE_IDX] = TYPE_ERROR_STATE;
data.resize(11); this->tx_header_[TX_DATA_IDX] = error;
data[6] = IMPROV_SERIAL_VERSION; this->write_data_();
data[7] = TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
} }
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) { void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'}; this->tx_header_[TX_TYPE_IDX] = TYPE_RPC_RESPONSE;
data.resize(9); this->write_data_(response.data(), response.size());
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
this->write_data_(data);
} }
void ImprovSerialComponent::on_wifi_connect_timeout_() { void ImprovSerialComponent::on_wifi_connect_timeout_() {
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT); this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
this->set_state_(improv::STATE_AUTHORIZED); this->set_state_(improv::STATE_AUTHORIZED);
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network"); ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
wifi::global_wifi_component->clear_sta(); wifi::global_wifi_component->clear_sta();
} }

View File

@@ -26,6 +26,16 @@
namespace esphome { namespace esphome {
namespace improv_serial { namespace improv_serial {
// TX buffer layout constants
static constexpr uint8_t TX_HEADER_SIZE = 6; // Bytes 0-5 = "IMPROV"
static constexpr uint8_t TX_VERSION_IDX = 6;
static constexpr uint8_t TX_TYPE_IDX = 7;
static constexpr uint8_t TX_LENGTH_IDX = 8;
static constexpr uint8_t TX_DATA_IDX = 9; // For state/error messages only
static constexpr uint8_t TX_CHECKSUM_IDX = 10;
static constexpr uint8_t TX_NEWLINE_IDX = 11;
static constexpr uint8_t TX_BUFFER_SIZE = 12;
enum ImprovSerialType : uint8_t { enum ImprovSerialType : uint8_t {
TYPE_CURRENT_STATE = 0x01, TYPE_CURRENT_STATE = 0x01,
TYPE_ERROR_STATE = 0x02, TYPE_ERROR_STATE = 0x02,
@@ -57,7 +67,22 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase {
std::vector<uint8_t> build_version_info_(); std::vector<uint8_t> build_version_info_();
optional<uint8_t> read_byte_(); optional<uint8_t> read_byte_();
void write_data_(std::vector<uint8_t> &data); void write_data_(const uint8_t *data = nullptr, size_t size = 0);
uint8_t tx_header_[TX_BUFFER_SIZE] = {
'I', // 0: Header
'M', // 1: Header
'P', // 2: Header
'R', // 3: Header
'O', // 4: Header
'V', // 5: Header
IMPROV_SERIAL_VERSION, // 6: Version
0, // 7: ImprovSerialType
0, // 8: Length
0, // 9...X: Data (here, one byte reserved for state/error)
0, // X + 10: Checksum
'\n',
};
#ifdef USE_ESP32 #ifdef USE_ESP32
uart_port_t uart_num_; uart_port_t uart_num_;

View File

@@ -199,6 +199,9 @@ async def component_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

@@ -27,8 +27,8 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_; uint8_t pin_;
bool inverted_; bool inverted_{};
gpio::Flags flags_; gpio::Flags flags_{};
}; };
} // namespace libretiny } // namespace libretiny

View File

@@ -30,7 +30,7 @@ inline static uint8_t half_sin8(uint8_t v) { return sin16_c(uint16_t(v) * 128u)
class AddressableLightEffect : public LightEffect { class AddressableLightEffect : public LightEffect {
public: public:
explicit AddressableLightEffect(const std::string &name) : LightEffect(name) {} explicit AddressableLightEffect(const char *name) : LightEffect(name) {}
void start_internal() override { void start_internal() override {
this->get_addressable_()->set_effect_active(true); this->get_addressable_()->set_effect_active(true);
this->get_addressable_()->clear_effect_data(); this->get_addressable_()->clear_effect_data();
@@ -57,10 +57,9 @@ class AddressableLightEffect : public LightEffect {
class AddressableLambdaLightEffect : public AddressableLightEffect { class AddressableLambdaLightEffect : public AddressableLightEffect {
public: public:
AddressableLambdaLightEffect(const std::string &name, AddressableLambdaLightEffect(const char *name, void (*f)(AddressableLight &, Color, bool initial_run),
std::function<void(AddressableLight &, Color, bool initial_run)> f,
uint32_t update_interval) uint32_t update_interval)
: AddressableLightEffect(name), f_(std::move(f)), update_interval_(update_interval) {} : AddressableLightEffect(name), f_(f), update_interval_(update_interval) {}
void start() override { this->initial_run_ = true; } void start() override { this->initial_run_ = true; }
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
const uint32_t now = millis(); const uint32_t now = millis();
@@ -73,7 +72,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
} }
protected: protected:
std::function<void(AddressableLight &, Color, bool initial_run)> f_; void (*f_)(AddressableLight &, Color, bool initial_run);
uint32_t update_interval_; uint32_t update_interval_;
uint32_t last_run_{0}; uint32_t last_run_{0};
bool initial_run_; bool initial_run_;
@@ -81,7 +80,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
class AddressableRainbowLightEffect : public AddressableLightEffect { class AddressableRainbowLightEffect : public AddressableLightEffect {
public: public:
explicit AddressableRainbowLightEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableRainbowLightEffect(const char *name) : AddressableLightEffect(name) {}
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
ESPHSVColor hsv; ESPHSVColor hsv;
hsv.value = 255; hsv.value = 255;
@@ -112,7 +111,7 @@ struct AddressableColorWipeEffectColor {
class AddressableColorWipeEffect : public AddressableLightEffect { class AddressableColorWipeEffect : public AddressableLightEffect {
public: public:
explicit AddressableColorWipeEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableColorWipeEffect(const char *name) : AddressableLightEffect(name) {}
void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; } void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; } void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
void set_reverse(bool reverse) { this->reverse_ = reverse; } void set_reverse(bool reverse) { this->reverse_ = reverse; }
@@ -165,7 +164,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
class AddressableScanEffect : public AddressableLightEffect { class AddressableScanEffect : public AddressableLightEffect {
public: public:
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableScanEffect(const char *name) : AddressableLightEffect(name) {}
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; } void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; } void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; }
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
@@ -202,7 +201,7 @@ class AddressableScanEffect : public AddressableLightEffect {
class AddressableTwinkleEffect : public AddressableLightEffect { class AddressableTwinkleEffect : public AddressableLightEffect {
public: public:
explicit AddressableTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
void apply(AddressableLight &addressable, const Color &current_color) override { void apply(AddressableLight &addressable, const Color &current_color) override {
const uint32_t now = millis(); const uint32_t now = millis();
uint8_t pos_add = 0; uint8_t pos_add = 0;
@@ -244,7 +243,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
class AddressableRandomTwinkleEffect : public AddressableLightEffect { class AddressableRandomTwinkleEffect : public AddressableLightEffect {
public: public:
explicit AddressableRandomTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableRandomTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
const uint32_t now = millis(); const uint32_t now = millis();
uint8_t pos_add = 0; uint8_t pos_add = 0;
@@ -293,7 +292,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
class AddressableFireworksEffect : public AddressableLightEffect { class AddressableFireworksEffect : public AddressableLightEffect {
public: public:
explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableFireworksEffect(const char *name) : AddressableLightEffect(name) {}
void start() override { void start() override {
auto &it = *this->get_addressable_(); auto &it = *this->get_addressable_();
it.all() = Color::BLACK; it.all() = Color::BLACK;
@@ -342,7 +341,7 @@ class AddressableFireworksEffect : public AddressableLightEffect {
class AddressableFlickerEffect : public AddressableLightEffect { class AddressableFlickerEffect : public AddressableLightEffect {
public: public:
explicit AddressableFlickerEffect(const std::string &name) : AddressableLightEffect(name) {} explicit AddressableFlickerEffect(const char *name) : AddressableLightEffect(name) {}
void apply(AddressableLight &it, const Color &current_color) override { void apply(AddressableLight &it, const Color &current_color) override {
const uint32_t now = millis(); const uint32_t now = millis();
const uint8_t intensity = this->intensity_; const uint8_t intensity = this->intensity_;

View File

@@ -17,7 +17,7 @@ inline static float random_cubic_float() {
/// Pulse effect. /// Pulse effect.
class PulseLightEffect : public LightEffect { class PulseLightEffect : public LightEffect {
public: public:
explicit PulseLightEffect(const std::string &name) : LightEffect(name) {} explicit PulseLightEffect(const char *name) : LightEffect(name) {}
void apply() override { void apply() override {
const uint32_t now = millis(); const uint32_t now = millis();
@@ -60,7 +60,7 @@ class PulseLightEffect : public LightEffect {
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them. /// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
class RandomLightEffect : public LightEffect { class RandomLightEffect : public LightEffect {
public: public:
explicit RandomLightEffect(const std::string &name) : LightEffect(name) {} explicit RandomLightEffect(const char *name) : LightEffect(name) {}
void apply() override { void apply() override {
const uint32_t now = millis(); const uint32_t now = millis();
@@ -112,8 +112,8 @@ class RandomLightEffect : public LightEffect {
class LambdaLightEffect : public LightEffect { class LambdaLightEffect : public LightEffect {
public: public:
LambdaLightEffect(const std::string &name, std::function<void(bool initial_run)> f, uint32_t update_interval) LambdaLightEffect(const char *name, void (*f)(bool initial_run), uint32_t update_interval)
: LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {} : LightEffect(name), f_(f), update_interval_(update_interval) {}
void start() override { this->initial_run_ = true; } void start() override { this->initial_run_ = true; }
void apply() override { void apply() override {
@@ -130,7 +130,7 @@ class LambdaLightEffect : public LightEffect {
uint32_t get_current_index() const { return this->get_index(); } uint32_t get_current_index() const { return this->get_index(); }
protected: protected:
std::function<void(bool initial_run)> f_; void (*f_)(bool initial_run);
uint32_t update_interval_; uint32_t update_interval_;
uint32_t last_run_{0}; uint32_t last_run_{0};
bool initial_run_; bool initial_run_;
@@ -138,7 +138,7 @@ class LambdaLightEffect : public LightEffect {
class AutomationLightEffect : public LightEffect { class AutomationLightEffect : public LightEffect {
public: public:
AutomationLightEffect(const std::string &name) : LightEffect(name) {} AutomationLightEffect(const char *name) : LightEffect(name) {}
void stop() override { this->trig_->stop_action(); } void stop() override { this->trig_->stop_action(); }
void apply() override { void apply() override {
if (!this->trig_->is_action_running()) { if (!this->trig_->is_action_running()) {
@@ -163,7 +163,7 @@ struct StrobeLightEffectColor {
class StrobeLightEffect : public LightEffect { class StrobeLightEffect : public LightEffect {
public: public:
explicit StrobeLightEffect(const std::string &name) : LightEffect(name) {} explicit StrobeLightEffect(const char *name) : LightEffect(name) {}
void apply() override { void apply() override {
const uint32_t now = millis(); const uint32_t now = millis();
if (now - this->last_switch_ < this->colors_[this->at_color_].duration) if (now - this->last_switch_ < this->colors_[this->at_color_].duration)
@@ -198,7 +198,7 @@ class StrobeLightEffect : public LightEffect {
class FlickerLightEffect : public LightEffect { class FlickerLightEffect : public LightEffect {
public: public:
explicit FlickerLightEffect(const std::string &name) : LightEffect(name) {} explicit FlickerLightEffect(const char *name) : LightEffect(name) {}
void apply() override { void apply() override {
LightColorValues remote = this->state_->remote_values; LightColorValues remote = this->state_->remote_values;

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include "esphome/core/finite_set_mask.h"
namespace esphome { namespace esphome {
namespace light { namespace light {
@@ -107,13 +108,9 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) {
// Type alias for raw color mode bitmask values // Type alias for raw color mode bitmask values
using color_mode_bitmask_t = uint16_t; using color_mode_bitmask_t = uint16_t;
// Constants for ColorMode count and bit range // Lookup table for ColorMode bit mapping
static constexpr int COLOR_MODE_COUNT = 10; // UNKNOWN through RGB_COLD_WARM_WHITE // This array defines the canonical order of color modes (bit 0-9)
static constexpr int MAX_BIT_INDEX = sizeof(color_mode_bitmask_t) * 8; // Number of bits in bitmask type constexpr ColorMode COLOR_MODE_LOOKUP[] = {
// Compile-time array of all ColorMode values in declaration order
// Bit positions (0-9) map directly to enum declaration order
static constexpr ColorMode COLOR_MODES[COLOR_MODE_COUNT] = {
ColorMode::UNKNOWN, // bit 0 ColorMode::UNKNOWN, // bit 0
ColorMode::ON_OFF, // bit 1 ColorMode::ON_OFF, // bit 1
ColorMode::BRIGHTNESS, // bit 2 ColorMode::BRIGHTNESS, // bit 2
@@ -126,33 +123,42 @@ static constexpr ColorMode COLOR_MODES[COLOR_MODE_COUNT] = {
ColorMode::RGB_COLD_WARM_WHITE, // bit 9 ColorMode::RGB_COLD_WARM_WHITE, // bit 9
}; };
/// Map ColorMode enum values to bit positions (0-9) /// Bit mapping policy for ColorMode
/// Bit positions follow the enum declaration order /// Uses lookup table for non-contiguous enum values
static constexpr int mode_to_bit(ColorMode mode) { struct ColorModeBitPolicy {
// Linear search through COLOR_MODES array using mask_t = uint16_t; // 10 bits requires uint16_t
// Compiler optimizes this to efficient code since array is constexpr static constexpr int MAX_BITS = sizeof(COLOR_MODE_LOOKUP) / sizeof(COLOR_MODE_LOOKUP[0]);
for (int i = 0; i < COLOR_MODE_COUNT; ++i) {
if (COLOR_MODES[i] == mode)
return i;
}
return 0;
}
/// Map bit positions (0-9) to ColorMode enum values static constexpr unsigned to_bit(ColorMode mode) {
/// Bit positions follow the enum declaration order // Linear search through lookup table
static constexpr ColorMode bit_to_mode(int bit) { // Compiler optimizes this to efficient code since array is constexpr
// Direct lookup in COLOR_MODES array for (int i = 0; i < MAX_BITS; ++i) {
return (bit >= 0 && bit < COLOR_MODE_COUNT) ? COLOR_MODES[bit] : ColorMode::UNKNOWN; if (COLOR_MODE_LOOKUP[i] == mode)
} return i;
}
return 0;
}
static constexpr ColorMode from_bit(unsigned bit) {
return (bit < MAX_BITS) ? COLOR_MODE_LOOKUP[bit] : ColorMode::UNKNOWN;
}
};
// Type alias for ColorMode bitmask using policy-based design
using ColorModeMask = FiniteSetMask<ColorMode, ColorModeBitPolicy>;
// Number of ColorCapability enum values
constexpr int COLOR_CAPABILITY_COUNT = 6;
/// Helper to compute capability bitmask at compile time /// Helper to compute capability bitmask at compile time
static constexpr color_mode_bitmask_t compute_capability_bitmask(ColorCapability capability) { constexpr uint16_t compute_capability_bitmask(ColorCapability capability) {
color_mode_bitmask_t mask = 0; uint16_t mask = 0;
uint8_t cap_bit = static_cast<uint8_t>(capability); uint8_t cap_bit = static_cast<uint8_t>(capability);
// Check each ColorMode to see if it has this capability // Check each ColorMode to see if it has this capability
for (int bit = 0; bit < COLOR_MODE_COUNT; ++bit) { constexpr int color_mode_count = sizeof(COLOR_MODE_LOOKUP) / sizeof(COLOR_MODE_LOOKUP[0]);
uint8_t mode_val = static_cast<uint8_t>(bit_to_mode(bit)); for (int bit = 0; bit < color_mode_count; ++bit) {
uint8_t mode_val = static_cast<uint8_t>(COLOR_MODE_LOOKUP[bit]);
if ((mode_val & cap_bit) != 0) { if ((mode_val & cap_bit) != 0) {
mask |= (1 << bit); mask |= (1 << bit);
} }
@@ -160,12 +166,9 @@ static constexpr color_mode_bitmask_t compute_capability_bitmask(ColorCapability
return mask; return mask;
} }
// Number of ColorCapability enum values
static constexpr int COLOR_CAPABILITY_COUNT = 6;
/// Compile-time lookup table mapping ColorCapability to bitmask /// Compile-time lookup table mapping ColorCapability to bitmask
/// This array is computed at compile time using constexpr /// This array is computed at compile time using constexpr
static constexpr color_mode_bitmask_t CAPABILITY_BITMASKS[] = { constexpr uint16_t CAPABILITY_BITMASKS[] = {
compute_capability_bitmask(ColorCapability::ON_OFF), // 1 << 0 compute_capability_bitmask(ColorCapability::ON_OFF), // 1 << 0
compute_capability_bitmask(ColorCapability::BRIGHTNESS), // 1 << 1 compute_capability_bitmask(ColorCapability::BRIGHTNESS), // 1 << 1
compute_capability_bitmask(ColorCapability::WHITE), // 1 << 2 compute_capability_bitmask(ColorCapability::WHITE), // 1 << 2
@@ -174,130 +177,38 @@ static constexpr color_mode_bitmask_t CAPABILITY_BITMASKS[] = {
compute_capability_bitmask(ColorCapability::RGB), // 1 << 5 compute_capability_bitmask(ColorCapability::RGB), // 1 << 5
}; };
/// Bitmask for storing a set of ColorMode values efficiently. /**
/// Replaces std::set<ColorMode> to eliminate red-black tree overhead (~586 bytes). * @brief Helper function to convert a power-of-2 ColorCapability value to an array index for CAPABILITY_BITMASKS
class ColorModeMask { * lookup.
public: *
constexpr ColorModeMask() = default; * This function maps ColorCapability values (1, 2, 4, 8, 16, 32) to array indices (0, 1, 2, 3, 4, 5).
* Used to index into the CAPABILITY_BITMASKS lookup table.
/// Support initializer list syntax: {ColorMode::RGB, ColorMode::WHITE} *
constexpr ColorModeMask(std::initializer_list<ColorMode> modes) { * @param capability A ColorCapability enum value (must be a power of 2).
for (auto mode : modes) { * @return The corresponding array index (0-based).
this->add(mode); */
} inline int capability_to_index(ColorCapability capability) {
} uint8_t cap_val = static_cast<uint8_t>(capability);
constexpr void add(ColorMode mode) { this->mask_ |= (1 << mode_to_bit(mode)); }
/// Add multiple modes at once using initializer list
constexpr void add(std::initializer_list<ColorMode> modes) {
for (auto mode : modes) {
this->add(mode);
}
}
constexpr bool contains(ColorMode mode) const { return (this->mask_ & (1 << mode_to_bit(mode))) != 0; }
constexpr size_t size() const {
// Count set bits using Brian Kernighan's algorithm
// More efficient for sparse bitmasks (typical case: 2-4 modes out of 10)
uint16_t n = this->mask_;
size_t count = 0;
while (n) {
n &= n - 1; // Clear the least significant set bit
count++;
}
return count;
}
constexpr bool empty() const { return this->mask_ == 0; }
/// Iterator support for API encoding
class Iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = ColorMode;
using difference_type = std::ptrdiff_t;
using pointer = const ColorMode *;
using reference = ColorMode;
constexpr Iterator(color_mode_bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
constexpr ColorMode operator*() const { return bit_to_mode(bit_); }
constexpr Iterator &operator++() {
++bit_;
advance_to_next_set_bit_();
return *this;
}
constexpr bool operator==(const Iterator &other) const { return bit_ == other.bit_; }
constexpr bool operator!=(const Iterator &other) const { return !(*this == other); }
private:
constexpr void advance_to_next_set_bit_() { bit_ = ColorModeMask::find_next_set_bit(mask_, bit_); }
color_mode_bitmask_t mask_;
int bit_;
};
constexpr Iterator begin() const { return Iterator(mask_, 0); }
constexpr Iterator end() const { return Iterator(mask_, MAX_BIT_INDEX); }
/// Get the raw bitmask value for API encoding
constexpr color_mode_bitmask_t get_mask() const { return this->mask_; }
/// Find the next set bit in a bitmask starting from a given position
/// Returns the bit position, or MAX_BIT_INDEX if no more bits are set
static constexpr int find_next_set_bit(color_mode_bitmask_t mask, int start_bit) {
int bit = start_bit;
while (bit < MAX_BIT_INDEX && !(mask & (1 << bit))) {
++bit;
}
return bit;
}
/// Find the first set bit in a bitmask and return the corresponding ColorMode
/// Used for optimizing compute_color_mode_() intersection logic
static constexpr ColorMode first_mode_from_mask(color_mode_bitmask_t mask) {
return bit_to_mode(find_next_set_bit(mask, 0));
}
/// Check if a ColorMode is present in a raw bitmask value
/// Useful for checking intersection results without creating a temporary ColorModeMask
static constexpr bool mask_contains(color_mode_bitmask_t mask, ColorMode mode) {
return (mask & (1 << mode_to_bit(mode))) != 0;
}
/// Check if any mode in the bitmask has a specific capability
/// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
bool has_capability(ColorCapability capability) const {
// Lookup the pre-computed bitmask for this capability and check intersection with our mask
// ColorCapability values: 1, 2, 4, 8, 16, 32 -> array indices: 0, 1, 2, 3, 4, 5
// We need to convert the power-of-2 value to an index
uint8_t cap_val = static_cast<uint8_t>(capability);
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
// Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n)) // Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n))
int index = __builtin_ctz(cap_val); return __builtin_ctz(cap_val);
#else #else
// Fallback for compilers without __builtin_ctz // Fallback for compilers without __builtin_ctz
int index = 0; int index = 0;
while (cap_val > 1) { while (cap_val > 1) {
cap_val >>= 1; cap_val >>= 1;
++index; ++index;
}
#endif
return (this->mask_ & CAPABILITY_BITMASKS[index]) != 0;
} }
return index;
#endif
}
private: /// Check if any mode in the bitmask has a specific capability
// Using uint16_t instead of uint32_t for more efficient iteration (fewer bits to scan). /// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
// Currently only 10 ColorMode values exist, so 16 bits is sufficient. inline bool has_capability(const ColorModeMask &mask, ColorCapability capability) {
// Can be changed to uint32_t if more than 16 color modes are needed in the future. // Lookup the pre-computed bitmask for this capability and check intersection with our mask
// Note: Due to struct padding, uint16_t and uint32_t result in same LightTraits size (12 bytes). return (mask.get_mask() & CAPABILITY_BITMASKS[capability_to_index(capability)]) != 0;
color_mode_bitmask_t mask_{0}; }
};
} // namespace light } // namespace light
} // namespace esphome } // namespace esphome

View File

@@ -156,7 +156,7 @@ void LightCall::perform() {
if (this->effect_ == 0u) { if (this->effect_ == 0u) {
effect_s = "None"; effect_s = "None";
} else { } else {
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str(); effect_s = this->parent_->effects_[this->effect_ - 1]->get_name();
} }
if (publish) { if (publish) {
@@ -437,7 +437,7 @@ ColorMode LightCall::compute_color_mode_() {
// Use the preferred suitable mode. // Use the preferred suitable mode.
if (intersection != 0) { if (intersection != 0) {
ColorMode mode = ColorModeMask::first_mode_from_mask(intersection); ColorMode mode = ColorModeMask::first_value_from_mask(intersection);
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(), ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(mode))); LOG_STR_ARG(color_mode_to_human(mode)));
return mode; return mode;
@@ -511,7 +511,7 @@ LightCall &LightCall::set_effect(const std::string &effect) {
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) { for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
LightEffect *e = this->parent_->effects_[i]; LightEffect *e = this->parent_->effects_[i];
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) { if (strcasecmp(effect.c_str(), e->get_name()) == 0) {
this->set_effect(i + 1); this->set_effect(i + 1);
found = true; found = true;
break; break;

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#include <utility>
#include "esphome/core/component.h" #include "esphome/core/component.h"
namespace esphome { namespace esphome {
@@ -11,7 +9,7 @@ class LightState;
class LightEffect { class LightEffect {
public: public:
explicit LightEffect(std::string name) : name_(std::move(name)) {} explicit LightEffect(const char *name) : name_(name) {}
/// Initialize this LightEffect. Will be called once after creation. /// Initialize this LightEffect. Will be called once after creation.
virtual void start() {} virtual void start() {}
@@ -24,7 +22,11 @@ class LightEffect {
/// Apply this effect. Use the provided state for starting transitions, ... /// Apply this effect. Use the provided state for starting transitions, ...
virtual void apply() = 0; virtual void apply() = 0;
const std::string &get_name() { return this->name_; } /**
* Returns the name of this effect.
* The returned pointer is valid for the lifetime of the program and must not be freed.
*/
const char *get_name() const { return this->name_; }
/// Internal method called by the LightState when this light effect is registered in it. /// Internal method called by the LightState when this light effect is registered in it.
virtual void init() {} virtual void init() {}
@@ -47,7 +49,7 @@ class LightEffect {
protected: protected:
LightState *state_{nullptr}; LightState *state_{nullptr};
std::string name_; const char *name_;
/// Internal method to find this effect's index in the parent light's effect list. /// Internal method to find this effect's index in the parent light's effect list.
uint32_t get_index_in_parent_() const; uint32_t get_index_in_parent_() const;

View File

@@ -178,12 +178,9 @@ void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; } void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
bool LightState::supports_effects() { return !this->effects_.empty(); } bool LightState::supports_effects() { return !this->effects_.empty(); }
const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; } const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
void LightState::add_effects(const std::vector<LightEffect *> &effects) { void LightState::add_effects(const std::initializer_list<LightEffect *> &effects) {
// Called once from Python codegen during setup with all effects from YAML config // Called once from Python codegen during setup with all effects from YAML config
this->effects_.init(effects.size()); this->effects_ = effects;
for (auto *effect : effects) {
this->effects_.push_back(effect);
}
} }
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); } void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }

View File

@@ -163,7 +163,7 @@ class LightState : public EntityBase, public Component {
const FixedVector<LightEffect *> &get_effects() const; const FixedVector<LightEffect *> &get_effects() const;
/// Add effects for this light state. /// Add effects for this light state.
void add_effects(const std::vector<LightEffect *> &effects); void add_effects(const std::initializer_list<LightEffect *> &effects);
/// Get the total number of effects available for this light. /// Get the total number of effects available for this light.
size_t get_effect_count() const { return this->effects_.size(); } size_t get_effect_count() const { return this->effects_.size(); }
@@ -177,7 +177,7 @@ class LightState : public EntityBase, public Component {
return 0; return 0;
} }
for (size_t i = 0; i < this->effects_.size(); i++) { for (size_t i = 0; i < this->effects_.size(); i++) {
if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name().c_str()) == 0) { if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name()) == 0) {
return i + 1; // Effects are 1-indexed in active_effect_index_ return i + 1; // Effects are 1-indexed in active_effect_index_
} }
} }

View File

@@ -26,9 +26,9 @@ class LightTraits {
this->supported_color_modes_ = ColorModeMask(modes); this->supported_color_modes_ = ColorModeMask(modes);
} }
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); } bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode) > 0; }
bool supports_color_capability(ColorCapability color_capability) const { bool supports_color_capability(ColorCapability color_capability) const {
return this->supported_color_modes_.has_capability(color_capability); return has_capability(this->supported_color_modes_, color_capability);
} }
float get_min_mireds() const { return this->min_mireds_; } float get_min_mireds() const { return this->min_mireds_; }

View File

@@ -1,7 +1,7 @@
import re import re
from esphome import automation from esphome import automation
from esphome.automation import LambdaAction from esphome.automation import LambdaAction, StatelessLambdaAction
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
@@ -173,14 +173,34 @@ def uart_selection(value):
raise NotImplementedError raise NotImplementedError
def validate_local_no_higher_than_global(value): def validate_local_no_higher_than_global(config):
global_level = LOG_LEVEL_SEVERITY.index(value[CONF_LEVEL]) global_level = config[CONF_LEVEL]
for tag, level in value.get(CONF_LOGS, {}).items(): global_level_index = LOG_LEVEL_SEVERITY.index(global_level)
if LOG_LEVEL_SEVERITY.index(level) > global_level: errs = []
raise cv.Invalid( for tag, level in config.get(CONF_LOGS, {}).items():
f"The configured log level for {tag} ({level}) must be no more severe than the global log level {value[CONF_LEVEL]}." if LOG_LEVEL_SEVERITY.index(level) > global_level_index:
errs.append(
cv.Invalid(
f"The configured log level for {tag} ({level}) must not be less severe than the global log level ({global_level})",
[CONF_LOGS, tag],
)
) )
return value if errs:
raise cv.MultipleInvalid(errs)
return config
def validate_initial_no_higher_than_global(config):
if initial_level := config.get(CONF_INITIAL_LEVEL):
global_level = config[CONF_LEVEL]
if LOG_LEVEL_SEVERITY.index(initial_level) > LOG_LEVEL_SEVERITY.index(
global_level
):
raise cv.Invalid(
f"The initial log level ({initial_level}) must not be less severe than the global log level ({global_level})",
[CONF_INITIAL_LEVEL],
)
return config
Logger = logger_ns.class_("Logger", cg.Component) Logger = logger_ns.class_("Logger", cg.Component)
@@ -263,6 +283,7 @@ CONFIG_SCHEMA = cv.All(
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
validate_local_no_higher_than_global, validate_local_no_higher_than_global,
validate_initial_no_higher_than_global,
) )
@@ -430,7 +451,9 @@ async def logger_log_action_to_code(config, action_id, template_arg, args):
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
@automation.register_action( @automation.register_action(
@@ -455,7 +478,9 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
text = str(cg.statement(logger.set_log_level(level))) text = str(cg.statement(logger.set_log_level(level)))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
FILTER_SOURCE_FILES = filter_source_files_from_platform( FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -358,7 +358,7 @@ class LvSelectable : public LvCompound {
virtual void set_selected_index(size_t index, lv_anim_enable_t anim) = 0; virtual void set_selected_index(size_t index, lv_anim_enable_t anim) = 0;
void set_selected_text(const std::string &text, lv_anim_enable_t anim); void set_selected_text(const std::string &text, lv_anim_enable_t anim);
std::string get_selected_text(); std::string get_selected_text();
std::vector<std::string> get_options() { return this->options_; } const std::vector<std::string> &get_options() { return this->options_; }
void set_options(std::vector<std::string> options); void set_options(std::vector<std::string> options);
protected: protected:

View File

@@ -53,7 +53,17 @@ class LVGLSelect : public select::Select, public Component {
this->widget_->set_selected_text(value, this->anim_); this->widget_->set_selected_text(value, this->anim_);
this->publish(); this->publish();
} }
void set_options_() { this->traits.set_options(this->widget_->get_options()); } void set_options_() {
// Widget uses std::vector<std::string>, SelectTraits uses FixedVector<const char*>
// Convert by extracting c_str() pointers
const auto &opts = this->widget_->get_options();
FixedVector<const char *> opt_ptrs;
opt_ptrs.init(opts.size());
for (size_t i = 0; i < opts.size(); i++) {
opt_ptrs[i] = opts[i].c_str();
}
this->traits.set_options(opt_ptrs);
}
LvSelectable *widget_; LvSelectable *widget_;
lv_anim_enable_t anim_; lv_anim_enable_t anim_;

View File

@@ -19,6 +19,9 @@ using climate::ClimateTraits;
using climate::ClimateMode; using climate::ClimateMode;
using climate::ClimateSwingMode; using climate::ClimateSwingMode;
using climate::ClimateFanMode; using climate::ClimateFanMode;
using climate::ClimateModeMask;
using climate::ClimateSwingModeMask;
using climate::ClimatePresetMask;
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate { class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
public: public:
@@ -40,20 +43,20 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
void do_power_on() { this->base_.setPowerState(true); } void do_power_on() { this->base_.setPowerState(true); }
void do_power_off() { this->base_.setPowerState(false); } void do_power_off() { this->base_.setPowerState(false); }
void do_power_toggle() { this->base_.setPowerState(this->mode == ClimateMode::CLIMATE_MODE_OFF); } void do_power_toggle() { this->base_.setPowerState(this->mode == ClimateMode::CLIMATE_MODE_OFF); }
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; } void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; } void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; } void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; } void set_custom_presets(const std::vector<std::string> &presets) { this->supported_custom_presets_ = presets; }
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; } void set_custom_fan_modes(const std::vector<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
protected: protected:
void control(const ClimateCall &call) override; void control(const ClimateCall &call) override;
ClimateTraits traits() override; ClimateTraits traits() override;
std::set<ClimateMode> supported_modes_{}; ClimateModeMask supported_modes_{};
std::set<ClimateSwingMode> supported_swing_modes_{}; ClimateSwingModeMask supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{}; ClimatePresetMask supported_presets_{};
std::set<std::string> supported_custom_presets_{}; std::vector<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{}; std::vector<std::string> supported_custom_fan_modes_{};
Sensor *outdoor_sensor_{nullptr}; Sensor *outdoor_sensor_{nullptr};
Sensor *humidity_sensor_{nullptr}; Sensor *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr}; Sensor *power_sensor_{nullptr};

View File

@@ -384,6 +384,18 @@ class DriverChip:
transform[CONF_TRANSFORM] = True transform[CONF_TRANSFORM] = True
return transform return transform
def swap_xy_schema(self):
uses_swap = self.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED
def validator(value):
if value:
raise cv.Invalid("Axis swapping not supported by this model")
return cv.boolean(value)
if uses_swap:
return {cv.Required(CONF_SWAP_XY): cv.boolean}
return {cv.Optional(CONF_SWAP_XY, default=False): validator}
def add_madctl(self, sequence: list, config: dict): def add_madctl(self, sequence: list, config: dict):
# Add the MADCTL command to the sequence based on the configuration. # Add the MADCTL command to the sequence based on the configuration.
use_flip = config.get(CONF_USE_AXIS_FLIPS) use_flip = config.get(CONF_USE_AXIS_FLIPS)

View File

@@ -46,6 +46,7 @@ from esphome.const import (
CONF_DATA_RATE, CONF_DATA_RATE,
CONF_DC_PIN, CONF_DC_PIN,
CONF_DIMENSIONS, CONF_DIMENSIONS,
CONF_DISABLED,
CONF_ENABLE_PIN, CONF_ENABLE_PIN,
CONF_GREEN, CONF_GREEN,
CONF_HSYNC_PIN, CONF_HSYNC_PIN,
@@ -117,16 +118,16 @@ def data_pin_set(length):
def model_schema(config): def model_schema(config):
model = MODELS[config[CONF_MODEL].upper()] model = MODELS[config[CONF_MODEL].upper()]
if transforms := model.transforms: transform = cv.Any(
transform = cv.Schema({cv.Required(x): cv.boolean for x in transforms}) cv.Schema(
for x in (CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y): {
if x not in transforms: cv.Required(CONF_MIRROR_X): cv.boolean,
transform = transform.extend( cv.Required(CONF_MIRROR_Y): cv.boolean,
{cv.Optional(x): cv.invalid(f"{x} not supported by this model")} **model.swap_xy_schema(),
) }
else: ),
transform = cv.invalid("This model does not support transforms") cv.one_of(CONF_DISABLED, lower=True),
)
# RPI model does not use an init sequence, indicates with empty list # RPI model does not use an init sequence, indicates with empty list
if model.initsequence is None: if model.initsequence is None:
# Custom model requires an init sequence # Custom model requires an init sequence
@@ -135,12 +136,16 @@ def model_schema(config):
else: else:
iseqconf = cv.Optional(CONF_INIT_SEQUENCE) iseqconf = cv.Optional(CONF_INIT_SEQUENCE)
uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0 uses_spi = CONF_INIT_SEQUENCE in config or len(model.initsequence) != 0
swap_xy = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY, False) # Dimensions are optional if the model has a default width and the x-y transform is not overridden
transform_config = config.get(CONF_TRANSFORM, {})
# Dimensions are optional if the model has a default width and the swap_xy transform is not overridden is_swapped = (
cv_dimensions = ( isinstance(transform_config, dict)
cv.Optional if model.get_default(CONF_WIDTH) and not swap_xy else cv.Required and transform_config.get(CONF_SWAP_XY, False) is True
) )
cv_dimensions = (
cv.Optional if model.get_default(CONF_WIDTH) and not is_swapped else cv.Required
)
pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18") pixel_modes = (PIXEL_MODE_16BIT, PIXEL_MODE_18BIT, "16", "18")
schema = display.FULL_DISPLAY_SCHEMA.extend( schema = display.FULL_DISPLAY_SCHEMA.extend(
{ {
@@ -157,7 +162,7 @@ def model_schema(config):
model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of( model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.one_of(
*pixel_modes, lower=True *pixel_modes, lower=True
), ),
model.option(CONF_TRANSFORM, cv.UNDEFINED): transform, cv.Optional(CONF_TRANSFORM): transform,
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True), cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
model.option(CONF_INVERT_COLORS, False): cv.boolean, model.option(CONF_INVERT_COLORS, False): cv.boolean,
model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean, model.option(CONF_USE_AXIS_FLIPS, True): cv.boolean,
@@ -270,7 +275,6 @@ async def to_code(config):
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH])) cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED])) cg.add(var.set_pclk_inverted(config[CONF_PCLK_INVERTED]))
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY])) cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY]))
index = 0
dpins = [] dpins = []
if CONF_RED in config[CONF_DATA_PINS]: if CONF_RED in config[CONF_DATA_PINS]:
red_pins = config[CONF_DATA_PINS][CONF_RED] red_pins = config[CONF_DATA_PINS][CONF_RED]

View File

@@ -131,19 +131,6 @@ def denominator(config):
) from StopIteration ) from StopIteration
def swap_xy_schema(model):
uses_swap = model.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED
def validator(value):
if value:
raise cv.Invalid("Axis swapping not supported by this model")
return cv.boolean(value)
if uses_swap:
return {cv.Required(CONF_SWAP_XY): cv.boolean}
return {cv.Optional(CONF_SWAP_XY, default=False): validator}
def model_schema(config): def model_schema(config):
model = MODELS[config[CONF_MODEL]] model = MODELS[config[CONF_MODEL]]
bus_mode = config[CONF_BUS_MODE] bus_mode = config[CONF_BUS_MODE]
@@ -152,7 +139,7 @@ def model_schema(config):
{ {
cv.Required(CONF_MIRROR_X): cv.boolean, cv.Required(CONF_MIRROR_X): cv.boolean,
cv.Required(CONF_MIRROR_Y): cv.boolean, cv.Required(CONF_MIRROR_Y): cv.boolean,
**swap_xy_schema(model), **model.swap_xy_schema(),
} }
), ),
cv.one_of(CONF_DISABLED, lower=True), cv.one_of(CONF_DISABLED, lower=True),

View File

@@ -33,8 +33,8 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor,
void dump_config() override; void dump_config() override;
using transform_func_t = std::function<optional<bool>(ModbusBinarySensor *, bool, const std::vector<uint8_t> &)>; using transform_func_t = optional<bool> (*)(ModbusBinarySensor *, bool, const std::vector<uint8_t> &);
void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_template(transform_func_t f) { this->transform_func_ = f; }
protected: protected:
optional<transform_func_t> transform_func_{nullopt}; optional<transform_func_t> transform_func_{nullopt};

View File

@@ -31,10 +31,10 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
void set_parent(ModbusController *parent) { this->parent_ = parent; } void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; } void set_write_multiply(float factor) { this->multiply_by_ = factor; }
using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>; using transform_func_t = optional<float> (*)(ModbusNumber *, float, const std::vector<uint8_t> &);
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>; using write_transform_func_t = optional<float> (*)(ModbusNumber *, float, std::vector<uint16_t> &);
void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_template(transform_func_t f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:

View File

@@ -29,8 +29,8 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
// Do nothing // Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{}; void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<float>(ModbusFloatOutput *, float, std::vector<uint16_t> &)>; using write_transform_func_t = optional<float> (*)(ModbusFloatOutput *, float, std::vector<uint16_t> &);
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:
@@ -60,8 +60,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
// Do nothing // Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{}; void parse_and_publish(const std::vector<uint8_t> &data) override{};
using write_transform_func_t = std::function<optional<bool>(ModbusBinaryOutput *, bool, std::vector<uint8_t> &)>; using write_transform_func_t = optional<bool> (*)(ModbusBinaryOutput *, bool, std::vector<uint8_t> &);
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:

View File

@@ -28,7 +28,7 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
if (map_it != this->mapping_.cend()) { if (map_it != this->mapping_.cend()) {
size_t idx = std::distance(this->mapping_.cbegin(), map_it); size_t idx = std::distance(this->mapping_.cbegin(), map_it);
new_state = this->traits.get_options()[idx]; new_state = std::string(this->option_at(idx));
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value); ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
} else { } else {
ESP_LOGE(TAG, "No option found for mapping %lld", value); ESP_LOGE(TAG, "No option found for mapping %lld", value);
@@ -41,10 +41,12 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
} }
void ModbusSelect::control(const std::string &value) { void ModbusSelect::control(const std::string &value) {
auto options = this->traits.get_options(); auto idx = this->index_of(value);
auto opt_it = std::find(options.cbegin(), options.cend(), value); if (!idx.has_value()) {
size_t idx = std::distance(options.cbegin(), opt_it); ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
optional<int64_t> mapval = this->mapping_[idx]; return;
}
optional<int64_t> mapval = this->mapping_[idx.value()];
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str()); ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
std::vector<uint16_t> data; std::vector<uint16_t> data;

View File

@@ -26,16 +26,15 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
this->mapping_ = std::move(mapping); this->mapping_ = std::move(mapping);
} }
using transform_func_t = using transform_func_t = optional<std::string> (*)(ModbusSelect *const, int64_t, const std::vector<uint8_t> &);
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>; using write_transform_func_t = optional<int64_t> (*)(ModbusSelect *const, const std::string &, int64_t,
using write_transform_func_t = std::vector<uint16_t> &);
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
void set_parent(ModbusController *const parent) { this->parent_ = parent; } void set_parent(ModbusController *const parent) { this->parent_ = parent; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_template(transform_func_t f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void dump_config() override; void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override; void parse_and_publish(const std::vector<uint8_t> &data) override;

View File

@@ -25,9 +25,9 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override; void parse_and_publish(const std::vector<uint8_t> &data) override;
void dump_config() override; void dump_config() override;
using transform_func_t = std::function<optional<float>(ModbusSensor *, float, const std::vector<uint8_t> &)>; using transform_func_t = optional<float> (*)(ModbusSensor *, float, const std::vector<uint8_t> &);
void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_template(transform_func_t f) { this->transform_func_ = f; }
protected: protected:
optional<transform_func_t> transform_func_{nullopt}; optional<transform_func_t> transform_func_{nullopt};

View File

@@ -34,10 +34,10 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override; void parse_and_publish(const std::vector<uint8_t> &data) override;
void set_parent(ModbusController *parent) { this->parent_ = parent; } void set_parent(ModbusController *parent) { this->parent_ = parent; }
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>; using transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, const std::vector<uint8_t> &);
using write_transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, std::vector<uint8_t> &)>; using write_transform_func_t = optional<bool> (*)(ModbusSwitch *, bool, std::vector<uint8_t> &);
void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; } void set_template(transform_func_t f) { this->publish_transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } void set_write_template(write_transform_func_t f) { this->write_transform_func_ = f; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:

View File

@@ -30,9 +30,8 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
void dump_config() override; void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override; void parse_and_publish(const std::vector<uint8_t> &data) override;
using transform_func_t = using transform_func_t = optional<std::string> (*)(ModbusTextSensor *, std::string, const std::vector<uint8_t> &);
std::function<optional<std::string>(ModbusTextSensor *, std::string, const std::vector<uint8_t> &)>; void set_template(transform_func_t f) { this->transform_func_ = f; }
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
protected: protected:
optional<transform_func_t> transform_func_{nullopt}; optional<transform_func_t> transform_func_{nullopt};

View File

@@ -1,3 +1,5 @@
import ipaddress
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv import esphome.config_validation as cv
@@ -10,6 +12,41 @@ AUTO_LOAD = ["mdns"]
network_ns = cg.esphome_ns.namespace("network") network_ns = cg.esphome_ns.namespace("network")
IPAddress = network_ns.class_("IPAddress") IPAddress = network_ns.class_("IPAddress")
def ip_address_literal(ip: str | int | None) -> cg.MockObj:
"""Generate an IPAddress with compile-time initialization instead of runtime parsing.
This function parses the IP address in Python during code generation and generates
a call to the 4-octet constructor (IPAddress(192, 168, 1, 1)) instead of the
string constructor (IPAddress("192.168.1.1")). This eliminates runtime string
parsing overhead and reduces flash usage on embedded systems.
Args:
ip: IP address as string (e.g., "192.168.1.1"), ipaddress.IPv4Address, or None
Returns:
IPAddress expression that uses 4-octet constructor for efficiency
"""
if ip is None:
return IPAddress(0, 0, 0, 0)
try:
# Parse using Python's ipaddress module
ip_obj = ipaddress.ip_address(ip)
except (ValueError, TypeError):
pass
else:
# Only support IPv4 for now
if isinstance(ip_obj, ipaddress.IPv4Address):
# Extract octets from the packed bytes representation
octets = ip_obj.packed
# Generate call to 4-octet constructor: IPAddress(192, 168, 1, 1)
return IPAddress(octets[0], octets[1], octets[2], octets[3])
# Fallback to string constructor if parsing fails
return IPAddress(str(ip))
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.SplitDefault( cv.SplitDefault(

View File

@@ -540,6 +540,23 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/ */
void goto_page(uint8_t page); void goto_page(uint8_t page);
/**
* Set the visibility of a component.
*
* @param component The component name.
* @param show True to show the component, false to hide it.
*
* @see show_component()
* @see hide_component()
*
* Example:
* ```cpp
* it.set_component_visibility("textview", true); // Equivalent to show_component("textview")
* it.set_component_visibility("textview", false); // Equivalent to hide_component("textview")
* ```
*/
void set_component_visibility(const char *component, bool show) override;
/** /**
* Hide a component. * Hide a component.
* @param component The component name. * @param component The component name.

View File

@@ -45,6 +45,7 @@ class NextionBase {
virtual void set_component_pressed_font_color(const char *component, Color color) = 0; virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
virtual void set_component_font(const char *component, uint8_t font_id) = 0; virtual void set_component_font(const char *component, uint8_t font_id) = 0;
virtual void set_component_visibility(const char *component, bool show) = 0;
virtual void show_component(const char *component) = 0; virtual void show_component(const char *component) = 0;
virtual void hide_component(const char *component) = 0; virtual void hide_component(const char *component) = 0;

View File

@@ -201,13 +201,13 @@ void Nextion::set_component_font(const char *component, uint8_t font_id) {
this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%" PRIu8, component, font_id); this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%" PRIu8, component, font_id);
} }
void Nextion::hide_component(const char *component) { void Nextion::set_component_visibility(const char *component, bool show) {
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component); this->add_no_result_to_queue_with_printf_("set_component_visibility", "vis %s,%d", component, show ? 1 : 0);
} }
void Nextion::show_component(const char *component) { void Nextion::hide_component(const char *component) { this->set_component_visibility(component, false); }
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
} void Nextion::show_component(const char *component) { this->set_component_visibility(component, true); }
void Nextion::enable_component_touch(const char *component) { void Nextion::enable_component_touch(const char *component) {
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component); this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);

View File

@@ -81,13 +81,11 @@ void NextionComponent::update_component_settings(bool force_update) {
this->component_flags_.visible_needs_update = false; this->component_flags_.visible_needs_update = false;
if (this->component_flags_.visible) { this->nextion_->set_component_visibility(name_to_send.c_str(), this->component_flags_.visible);
this->nextion_->show_component(name_to_send.c_str()); if (!this->component_flags_.visible) {
this->send_state_to_nextion();
} else {
this->nextion_->hide_component(name_to_send.c_str());
return; return;
} }
this->send_state_to_nextion();
} }
if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) { if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) {

View File

@@ -174,11 +174,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported // Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate(); this->original_baud_rate_ = this->parent_->get_baud_rate();
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
115200, 230400, 250000, 256000, 512000, 921600};
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client // Define the configuration for the HTTP client

View File

@@ -177,11 +177,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported // Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate(); this->original_baud_rate_ = this->parent_->get_baud_rate();
static const std::vector<uint32_t> SUPPORTED_BAUD_RATES = {2400, 4800, 9600, 19200, 31250, 38400, 57600,
115200, 230400, 250000, 256000, 512000, 921600};
if (std::find(SUPPORTED_BAUD_RATES.begin(), SUPPORTED_BAUD_RATES.end(), baud_rate) == SUPPORTED_BAUD_RATES.end()) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate); ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client // Define the configuration for the HTTP client

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from pathlib import Path from pathlib import Path
@@ -277,3 +278,19 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
raise EsphomeError(f"Upload failed with result: {result}") raise EsphomeError(f"Upload failed with result: {result}")
return handled return handled
def show_logs(config: ConfigType, args, devices: list[str]) -> bool:
address = devices[0]
from .ble_logger import is_mac_address, logger_connect, logger_scan
if devices[0] == "BLE":
ble_device = asyncio.run(logger_scan(CORE.config["esphome"]["name"]))
if ble_device:
address = ble_device.address
else:
return True
if is_mac_address(address):
asyncio.run(logger_connect(address))
return True
return False

View File

@@ -0,0 +1,60 @@
import asyncio
import logging
import re
from typing import Final
from bleak import BleakClient, BleakScanner, BLEDevice
from bleak.exc import (
BleakCharacteristicNotFoundError,
BleakDBusError,
BleakDeviceNotFoundError,
)
_LOGGER = logging.getLogger(__name__)
NUS_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
NUS_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
MAC_ADDRESS_PATTERN: Final = re.compile(
r"([0-9A-F]{2}[:]){5}[0-9A-F]{2}$", flags=re.IGNORECASE
)
def is_mac_address(value: str) -> bool:
return MAC_ADDRESS_PATTERN.match(value)
async def logger_scan(name: str) -> BLEDevice | None:
_LOGGER.info("Scanning bluetooth for %s...", name)
device = await BleakScanner.find_device_by_name(name)
if not device:
_LOGGER.error("%s Bluetooth LE device was not found!", name)
return device
async def logger_connect(host: str) -> int | None:
disconnected_event = asyncio.Event()
def handle_disconnect(client):
disconnected_event.set()
def handle_rx(_, data: bytearray):
print(data.decode("utf-8"), end="")
_LOGGER.info("Connecting %s...", host)
try:
async with BleakClient(host, disconnected_callback=handle_disconnect) as client:
_LOGGER.info("Connected %s...", host)
try:
await client.start_notify(NUS_TX_CHAR_UUID, handle_rx)
except BleakDBusError as e:
_LOGGER.error("Bluetooth LE logger: %s", e)
disconnected_event.set()
await disconnected_event.wait()
except BleakDeviceNotFoundError:
_LOGGER.error("Device %s not found", host)
return 1
except BleakCharacteristicNotFoundError:
_LOGGER.error("Device %s has no NUS characteristic", host)
return 1

View File

@@ -74,6 +74,9 @@ async def nrf52_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
num = config[CONF_NUMBER] num = config[CONF_NUMBER]
cg.add(var.set_pin(num)) cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED])) # Only set if true to avoid bloating setup() function
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var return var

View File

@@ -252,7 +252,10 @@ async def setup_number_core_(
cg.add(var.traits.set_max_value(max_value)) cg.add(var.traits.set_max_value(max_value))
cg.add(var.traits.set_step(step)) cg.add(var.traits.set_step(step))
cg.add(var.traits.set_mode(config[CONF_MODE])) # Only set if non-default to avoid bloating setup() function
# (mode_ is initialized to NUMBER_MODE_AUTO in the header)
if config[CONF_MODE] != NumberMode.NUMBER_MODE_AUTO:
cg.add(var.traits.set_mode(config[CONF_MODE]))
for conf in config.get(CONF_ON_VALUE, []): for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

Some files were not shown because too many files have changed in this diff Show More