1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

Merge remote-tracking branch 'upstream/string_ref_for_all_incoming_api_strings' into integration

This commit is contained in:
J. Nick Koston
2025-12-25 09:02:03 -10:00
22 changed files with 537 additions and 297 deletions

View File

@@ -519,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/uart/event/* @eoasmxd
esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli

View File

@@ -102,7 +102,7 @@ message HelloRequest {
// For example "Home Assistant"
// Not strictly necessary to send but nice for debugging
// purposes.
string client_info = 1 [(pointer_to_buffer) = true];
string client_info = 1;
uint32 api_version_major = 2;
uint32 api_version_minor = 3;
}
@@ -139,7 +139,7 @@ message AuthenticationRequest {
option (ifdef) = "USE_API_PASSWORD";
// The password to log in with
string password = 1 [(pointer_to_buffer) = true];
string password = 1;
}
// Confirmation of successful connection. After this the connection is available for all traffic.
@@ -477,7 +477,7 @@ message FanCommandRequest {
bool has_speed_level = 10;
int32 speed_level = 11;
bool has_preset_mode = 12;
string preset_mode = 13 [(pointer_to_buffer) = true];
string preset_mode = 13;
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
}
@@ -579,7 +579,7 @@ message LightCommandRequest {
bool has_flash_length = 16;
uint32 flash_length = 17;
bool has_effect = 18;
string effect = 19 [(pointer_to_buffer) = true];
string effect = 19;
uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"];
}
@@ -824,9 +824,9 @@ message HomeAssistantStateResponse {
option (no_delay) = true;
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
string entity_id = 1 [(pointer_to_buffer) = true];
string state = 2 [(pointer_to_buffer) = true];
string attribute = 3 [(pointer_to_buffer) = true];
string entity_id = 1;
string state = 2;
string attribute = 3;
}
// ==================== IMPORT TIME ====================
@@ -841,7 +841,7 @@ message GetTimeResponse {
option (no_delay) = true;
fixed32 epoch_seconds = 1;
string timezone = 2 [(pointer_to_buffer) = true];
string timezone = 2;
}
// ==================== USER-DEFINES SERVICES ====================
@@ -1091,11 +1091,11 @@ message ClimateCommandRequest {
bool has_swing_mode = 14;
ClimateSwingMode swing_mode = 15;
bool has_custom_fan_mode = 16;
string custom_fan_mode = 17 [(pointer_to_buffer) = true];
string custom_fan_mode = 17;
bool has_preset = 18;
ClimatePreset preset = 19;
bool has_custom_preset = 20;
string custom_preset = 21 [(pointer_to_buffer) = true];
string custom_preset = 21;
bool has_target_humidity = 22;
float target_humidity = 23;
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
@@ -1274,7 +1274,7 @@ message SelectCommandRequest {
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
string state = 2 [(pointer_to_buffer) = true];
string state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}

View File

@@ -474,7 +474,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
if (msg.has_preset_mode)
call.set_preset_mode(reinterpret_cast<const char *>(msg.preset_mode), msg.preset_mode_len);
call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
call.perform();
}
#endif
@@ -560,7 +560,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
if (msg.has_flash_length)
call.set_flash_length(msg.flash_length);
if (msg.has_effect)
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len);
call.set_effect(msg.effect.c_str(), msg.effect.size());
call.perform();
}
#endif
@@ -739,11 +739,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
if (msg.has_fan_mode)
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
if (msg.has_custom_fan_mode)
call.set_fan_mode(reinterpret_cast<const char *>(msg.custom_fan_mode), msg.custom_fan_mode_len);
call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
if (msg.has_preset)
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset)
call.set_preset(reinterpret_cast<const char *>(msg.custom_preset), msg.custom_preset_len);
call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
call.perform();
@@ -932,7 +932,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
call.set_option(reinterpret_cast<const char *>(msg.state), msg.state_len);
call.set_option(msg.state.c_str(), msg.state.size());
call.perform();
}
#endif
@@ -1154,9 +1154,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
if (homeassistant::global_homeassistant_time != nullptr) {
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
#ifdef USE_TIME_TIMEZONE
if (value.timezone_len > 0) {
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone),
value.timezone_len);
if (!value.timezone.empty()) {
homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size());
}
#endif
}
@@ -1524,7 +1523,7 @@ void APIConnection::complete_authentication_() {
}
bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size());
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
char peername[socket::PEERNAME_MAX_LEN];
@@ -1553,7 +1552,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
AuthenticationResponse resp;
// bool invalid_password = 1;
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
resp.invalid_password = !this->parent_->check_password(msg.password.byte(), msg.password.size());
if (!resp.invalid_password) {
this->complete_authentication_();
}
@@ -1696,27 +1695,28 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
// Skip if entity_id is empty (invalid message)
if (msg.entity_id_len == 0) {
if (msg.entity_id.empty()) {
return;
}
for (auto &it : this->parent_->get_state_subs()) {
// Compare entity_id: check length matches and content matches
size_t entity_id_len = strlen(it.entity_id);
if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) {
if (entity_id_len != msg.entity_id.size() ||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
continue;
}
// Compare attribute: either both have matching attribute, or both have none
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
if (sub_attr_len != msg.attribute_len ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) {
if (sub_attr_len != msg.attribute.size() ||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
continue;
}
// Create temporary string for callback (callback takes const std::string &)
// Handle empty state (nullptr with len=0)
std::string state(msg.state_len > 0 ? reinterpret_cast<const char *>(msg.state) : "", msg.state_len);
// Handle empty state
std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size());
it.callback(state);
}
}

View File

@@ -23,9 +23,7 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->client_info = value.data();
this->client_info_len = value.size();
this->client_info = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -49,9 +47,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->password = value.data();
this->password_len = value.size();
this->password = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -448,9 +444,7 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 13: {
// Use raw data directly to avoid allocation
this->preset_mode = value.data();
this->preset_mode_len = value.size();
this->preset_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -615,9 +609,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 19: {
// Use raw data directly to avoid allocation
this->effect = value.data();
this->effect_len = value.size();
this->effect = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -859,7 +851,6 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->key = value.data();
this->key_len = value.size();
break;
@@ -936,12 +927,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v
}
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3:
this->error_message = value.as_string();
case 3: {
this->error_message = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
case 4: {
// Use raw data directly to avoid allocation
this->response_data = value.data();
this->response_data_len = value.size();
break;
@@ -967,21 +958,15 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->entity_id = value.data();
this->entity_id_len = value.size();
this->entity_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
case 2: {
// Use raw data directly to avoid allocation
this->state = value.data();
this->state_len = value.size();
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
case 3: {
// Use raw data directly to avoid allocation
this->attribute = value.data();
this->attribute_len = value.size();
this->attribute = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -993,9 +978,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->timezone = value.data();
this->timezone_len = value.size();
this->timezone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -1060,9 +1043,10 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4:
this->string_ = value.as_string();
case 4: {
this->string_ = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
case 9:
this->string_array.push_back(value.as_string());
break;
@@ -1153,7 +1137,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->success);
size.add_length(1, this->error_message_ref_.size());
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
size.add_length(4, this->response_data_len);
size.add_length(1, this->response_data_len);
#endif
}
#endif
@@ -1408,15 +1392,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 17: {
// Use raw data directly to avoid allocation
this->custom_fan_mode = value.data();
this->custom_fan_mode_len = value.size();
this->custom_fan_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
case 21: {
// Use raw data directly to avoid allocation
this->custom_preset = value.data();
this->custom_preset_len = value.size();
this->custom_preset = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -1702,9 +1682,7 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->state = value.data();
this->state_len = value.size();
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
@@ -1808,9 +1786,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 5:
this->tone = value.as_string();
case 5: {
this->tone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -1899,9 +1878,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4:
this->code = value.as_string();
case 4: {
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2069,9 +2049,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
}
bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 7:
this->media_url = value.as_string();
case 7: {
this->media_url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2279,7 +2260,6 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -2318,7 +2298,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -2502,12 +2481,14 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->name = value.as_string();
case 1: {
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 2:
this->value = value.as_string();
}
case 2: {
this->value = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2583,12 +2564,14 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar
}
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2:
this->timer_id = value.as_string();
case 2: {
this->timer_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 3:
this->name = value.as_string();
}
case 3: {
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2606,15 +2589,18 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt
}
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->media_id = value.as_string();
case 1: {
this->media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 2:
this->text = value.as_string();
}
case 2: {
this->text = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 3:
this->preannounce_media_id = value.as_string();
}
case 3: {
this->preannounce_media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2650,24 +2636,29 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn
}
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->id = value.as_string();
case 1: {
this->id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 2:
this->wake_word = value.as_string();
}
case 2: {
this->wake_word = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
case 3:
this->trained_languages.push_back(value.as_string());
break;
case 4:
this->model_type = value.as_string();
case 4: {
this->model_type = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 6:
this->model_hash = value.as_string();
}
case 6: {
this->model_hash = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
case 7:
this->url = value.as_string();
}
case 7: {
this->url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2777,9 +2768,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI
}
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 3:
this->code = value.as_string();
case 3: {
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -2861,9 +2853,10 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2:
this->state = value.as_string();
case 2: {
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}
default:
return false;
}
@@ -3331,7 +3324,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -3356,7 +3348,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
@@ -3372,7 +3363,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
}
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->type));
size.add_length(2, this->data_len);
size.add_length(1, this->data_len);
}
#endif

View File

@@ -357,12 +357,11 @@ class CommandProtoMessage : public ProtoDecodableMessage {
class HelloRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 1;
static constexpr uint8_t ESTIMATED_SIZE = 27;
static constexpr uint8_t ESTIMATED_SIZE = 17;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "hello_request"; }
#endif
const uint8_t *client_info{nullptr};
uint16_t client_info_len{0};
StringRef client_info{};
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -398,12 +397,11 @@ class HelloResponse final : public ProtoMessage {
class AuthenticationRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 3;
static constexpr uint8_t ESTIMATED_SIZE = 19;
static constexpr uint8_t ESTIMATED_SIZE = 9;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "authentication_request"; }
#endif
const uint8_t *password{nullptr};
uint16_t password_len{0};
StringRef password{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -784,7 +782,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
class FanCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 31;
static constexpr uint8_t ESTIMATED_SIZE = 48;
static constexpr uint8_t ESTIMATED_SIZE = 38;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "fan_command_request"; }
#endif
@@ -797,8 +795,7 @@ class FanCommandRequest final : public CommandProtoMessage {
bool has_speed_level{false};
int32_t speed_level{0};
bool has_preset_mode{false};
const uint8_t *preset_mode{nullptr};
uint16_t preset_mode_len{0};
StringRef preset_mode{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -860,7 +857,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
class LightCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 32;
static constexpr uint8_t ESTIMATED_SIZE = 122;
static constexpr uint8_t ESTIMATED_SIZE = 112;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "light_command_request"; }
#endif
@@ -889,8 +886,7 @@ class LightCommandRequest final : public CommandProtoMessage {
bool has_flash_length{false};
uint32_t flash_length{0};
bool has_effect{false};
const uint8_t *effect{nullptr};
uint16_t effect_len{0};
StringRef effect{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1171,7 +1167,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
#endif
uint32_t call_id{0};
bool success{false};
std::string error_message{};
StringRef error_message{};
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
const uint8_t *response_data{nullptr};
uint16_t response_data_len{0};
@@ -1222,16 +1218,13 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
class HomeAssistantStateResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 40;
static constexpr uint8_t ESTIMATED_SIZE = 57;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "home_assistant_state_response"; }
#endif
const uint8_t *entity_id{nullptr};
uint16_t entity_id_len{0};
const uint8_t *state{nullptr};
uint16_t state_len{0};
const uint8_t *attribute{nullptr};
uint16_t attribute_len{0};
StringRef entity_id{};
StringRef state{};
StringRef attribute{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1256,13 +1249,12 @@ class GetTimeRequest final : public ProtoMessage {
class GetTimeResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 37;
static constexpr uint8_t ESTIMATED_SIZE = 24;
static constexpr uint8_t ESTIMATED_SIZE = 14;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "get_time_response"; }
#endif
uint32_t epoch_seconds{0};
const uint8_t *timezone{nullptr};
uint16_t timezone_len{0};
StringRef timezone{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1310,7 +1302,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
bool bool_{false};
int32_t legacy_int{0};
float float_{0.0f};
std::string string_{};
StringRef string_{};
int32_t int_{0};
FixedVector<bool> bool_array{};
FixedVector<int32_t> int_array{};
@@ -1499,7 +1491,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
class ClimateCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 48;
static constexpr uint8_t ESTIMATED_SIZE = 104;
static constexpr uint8_t ESTIMATED_SIZE = 84;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "climate_command_request"; }
#endif
@@ -1516,13 +1508,11 @@ class ClimateCommandRequest final : public CommandProtoMessage {
bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false};
const uint8_t *custom_fan_mode{nullptr};
uint16_t custom_fan_mode_len{0};
StringRef custom_fan_mode{};
bool has_preset{false};
enums::ClimatePreset preset{};
bool has_custom_preset{false};
const uint8_t *custom_preset{nullptr};
uint16_t custom_preset_len{0};
StringRef custom_preset{};
bool has_target_humidity{false};
float target_humidity{0.0f};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -1695,12 +1685,11 @@ class SelectStateResponse final : public StateResponseProtoMessage {
class SelectCommandRequest final : public CommandProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 54;
static constexpr uint8_t ESTIMATED_SIZE = 28;
static constexpr uint8_t ESTIMATED_SIZE = 18;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_command_request"; }
#endif
const uint8_t *state{nullptr};
uint16_t state_len{0};
StringRef state{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1756,7 +1745,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
bool has_state{false};
bool state{false};
bool has_tone{false};
std::string tone{};
StringRef tone{};
bool has_duration{false};
uint32_t duration{0};
bool has_volume{false};
@@ -1817,7 +1806,7 @@ class LockCommandRequest final : public CommandProtoMessage {
#endif
enums::LockCommand command{};
bool has_code{false};
std::string code{};
StringRef code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -1927,7 +1916,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
bool has_volume{false};
float volume{0.0f};
bool has_media_url{false};
std::string media_url{};
StringRef media_url{};
bool has_announcement{false};
bool announcement{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -2503,8 +2492,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
};
class VoiceAssistantEventData final : public ProtoDecodableMessage {
public:
std::string name{};
std::string value{};
StringRef name{};
StringRef value{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2562,8 +2551,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
const char *message_name() const override { return "voice_assistant_timer_event_response"; }
#endif
enums::VoiceAssistantTimerEvent event_type{};
std::string timer_id{};
std::string name{};
StringRef timer_id{};
StringRef name{};
uint32_t total_seconds{0};
uint32_t seconds_left{0};
bool is_active{false};
@@ -2582,9 +2571,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_announce_request"; }
#endif
std::string media_id{};
std::string text{};
std::string preannounce_media_id{};
StringRef media_id{};
StringRef text{};
StringRef preannounce_media_id{};
bool start_conversation{false};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -2627,13 +2616,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
};
class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
public:
std::string id{};
std::string wake_word{};
StringRef id{};
StringRef wake_word{};
std::vector<std::string> trained_languages{};
std::string model_type{};
StringRef model_type{};
uint32_t model_size{0};
std::string model_hash{};
std::string url{};
StringRef model_hash{};
StringRef url{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2734,7 +2723,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
const char *message_name() const override { return "alarm_control_panel_command_request"; }
#endif
enums::AlarmControlPanelStateCommand command{};
std::string code{};
StringRef code{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@@ -2791,7 +2780,7 @@ class TextCommandRequest final : public CommandProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_command_request"; }
#endif
std::string state{};
StringRef state{};
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif

View File

@@ -736,7 +736,7 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
void HelloRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HelloRequest");
out.append(" client_info: ");
out.append(format_hex_pretty(this->client_info, this->client_info_len));
out.append("'").append(this->client_info.c_str(), this->client_info.size()).append("'");
out.append("\n");
dump_field(out, "api_version_major", this->api_version_major);
dump_field(out, "api_version_minor", this->api_version_minor);
@@ -752,7 +752,7 @@ void HelloResponse::dump_to(std::string &out) const {
void AuthenticationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AuthenticationRequest");
out.append(" password: ");
out.append(format_hex_pretty(this->password, this->password_len));
out.append("'").append(this->password.c_str(), this->password.size()).append("'");
out.append("\n");
}
void AuthenticationResponse::dump_to(std::string &out) const {
@@ -965,7 +965,7 @@ void FanCommandRequest::dump_to(std::string &out) const {
dump_field(out, "speed_level", this->speed_level);
dump_field(out, "has_preset_mode", this->has_preset_mode);
out.append(" preset_mode: ");
out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len));
out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1043,7 +1043,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
dump_field(out, "flash_length", this->flash_length);
dump_field(out, "has_effect", this->has_effect);
out.append(" effect: ");
out.append(format_hex_pretty(this->effect, this->effect_len));
out.append("'").append(this->effect.c_str(), this->effect.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1205,7 +1205,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantActionResponse");
dump_field(out, "call_id", this->call_id);
dump_field(out, "success", this->success);
dump_field(out, "error_message", this->error_message);
out.append(" error_message: ");
out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'");
out.append("\n");
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
out.append(" response_data: ");
out.append(format_hex_pretty(this->response_data, this->response_data_len));
@@ -1226,13 +1228,13 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
void HomeAssistantStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
out.append(" entity_id: ");
out.append(format_hex_pretty(this->entity_id, this->entity_id_len));
out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'");
out.append("\n");
out.append(" state: ");
out.append(format_hex_pretty(this->state, this->state_len));
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
out.append(" attribute: ");
out.append(format_hex_pretty(this->attribute, this->attribute_len));
out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'");
out.append("\n");
}
#endif
@@ -1241,7 +1243,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "GetTimeResponse");
dump_field(out, "epoch_seconds", this->epoch_seconds);
out.append(" timezone: ");
out.append(format_hex_pretty(this->timezone, this->timezone_len));
out.append("'").append(this->timezone.c_str(), this->timezone.size()).append("'");
out.append("\n");
}
#ifdef USE_API_USER_DEFINED_ACTIONS
@@ -1266,7 +1268,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "bool_", this->bool_);
dump_field(out, "legacy_int", this->legacy_int);
dump_field(out, "float_", this->float_);
dump_field(out, "string_", this->string_);
out.append(" string_: ");
out.append("'").append(this->string_.c_str(), this->string_.size()).append("'");
out.append("\n");
dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) {
dump_field(out, "bool_array", static_cast<bool>(it), 4);
@@ -1424,13 +1428,13 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
out.append(" custom_fan_mode: ");
out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len));
out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'");
out.append("\n");
dump_field(out, "has_preset", this->has_preset);
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
dump_field(out, "has_custom_preset", this->has_custom_preset);
out.append(" custom_preset: ");
out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len));
out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'");
out.append("\n");
dump_field(out, "has_target_humidity", this->has_target_humidity);
dump_field(out, "target_humidity", this->target_humidity);
@@ -1558,7 +1562,7 @@ void SelectCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "SelectCommandRequest");
dump_field(out, "key", this->key);
out.append(" state: ");
out.append(format_hex_pretty(this->state, this->state_len));
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
@@ -1599,7 +1603,9 @@ void SirenCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_state", this->has_state);
dump_field(out, "state", this->state);
dump_field(out, "has_tone", this->has_tone);
dump_field(out, "tone", this->tone);
out.append(" tone: ");
out.append("'").append(this->tone.c_str(), this->tone.size()).append("'");
out.append("\n");
dump_field(out, "has_duration", this->has_duration);
dump_field(out, "duration", this->duration);
dump_field(out, "has_volume", this->has_volume);
@@ -1641,7 +1647,9 @@ void LockCommandRequest::dump_to(std::string &out) const {
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::LockCommand>(this->command));
dump_field(out, "has_code", this->has_code);
dump_field(out, "code", this->code);
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -1719,7 +1727,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
dump_field(out, "has_volume", this->has_volume);
dump_field(out, "volume", this->volume);
dump_field(out, "has_media_url", this->has_media_url);
dump_field(out, "media_url", this->media_url);
out.append(" media_url: ");
out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'");
out.append("\n");
dump_field(out, "has_announcement", this->has_announcement);
dump_field(out, "announcement", this->announcement);
#ifdef USE_DEVICES
@@ -1949,8 +1959,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
}
void VoiceAssistantEventData::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventData");
dump_field(out, "name", this->name);
dump_field(out, "value", this->value);
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
out.append(" value: ");
out.append("'").append(this->value.c_str(), this->value.size()).append("'");
out.append("\n");
}
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantEventResponse");
@@ -1975,17 +1989,27 @@ void VoiceAssistantAudio::dump_to(std::string &out) const {
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse");
dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type));
dump_field(out, "timer_id", this->timer_id);
dump_field(out, "name", this->name);
out.append(" timer_id: ");
out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
out.append("\n");
dump_field(out, "total_seconds", this->total_seconds);
dump_field(out, "seconds_left", this->seconds_left);
dump_field(out, "is_active", this->is_active);
}
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest");
dump_field(out, "media_id", this->media_id);
dump_field(out, "text", this->text);
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
out.append(" media_id: ");
out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'");
out.append("\n");
out.append(" text: ");
out.append("'").append(this->text.c_str(), this->text.size()).append("'");
out.append("\n");
out.append(" preannounce_media_id: ");
out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'");
out.append("\n");
dump_field(out, "start_conversation", this->start_conversation);
}
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
@@ -1999,15 +2023,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
}
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
dump_field(out, "id", this->id);
dump_field(out, "wake_word", this->wake_word);
out.append(" id: ");
out.append("'").append(this->id.c_str(), this->id.size()).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'");
out.append("\n");
for (const auto &it : this->trained_languages) {
dump_field(out, "trained_languages", it, 4);
}
dump_field(out, "model_type", this->model_type);
out.append(" model_type: ");
out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'");
out.append("\n");
dump_field(out, "model_size", this->model_size);
dump_field(out, "model_hash", this->model_hash);
dump_field(out, "url", this->url);
out.append(" model_hash: ");
out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'");
out.append("\n");
out.append(" url: ");
out.append("'").append(this->url.c_str(), this->url.size()).append("'");
out.append("\n");
}
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
@@ -2066,7 +2100,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command));
dump_field(out, "code", this->code);
out.append(" code: ");
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif
@@ -2103,7 +2139,9 @@ void TextStateResponse::dump_to(std::string &out) const {
void TextCommandRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "TextCommandRequest");
dump_field(out, "key", this->key);
dump_field(out, "state", this->state);
out.append(" state: ");
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
out.append("\n");
#ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id);
#endif

View File

@@ -169,14 +169,16 @@ void CC1101Component::loop() {
}
// Read packet
uint8_t payload_length;
uint8_t payload_length, expected_rx;
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->read_(Register::FIFO, &payload_length, 1);
expected_rx = payload_length + 1;
} else {
payload_length = this->state_.PKTLEN;
expected_rx = payload_length;
}
if (payload_length == 0 || payload_length > 64) {
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
@@ -186,13 +188,12 @@ void CC1101Component::loop() {
this->packet_.resize(payload_length);
this->read_(Register::FIFO, this->packet_.data(), payload_length);
// Read status and trigger
uint8_t status[2];
this->read_(Register::FIFO, status, 2);
int8_t rssi_raw = static_cast<int8_t>(status[0]);
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = status[1] & STATUS_LQI_MASK;
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
this->read_(Register::RSSI);
this->read_(Register::LQI);
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
if (this->state_.CRC_EN == 0 || crc_ok) {
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
}
@@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) {
this->state_.GDO0_CFG = 0x01;
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
this->state_.FIFO_THR = 15;
// Don't append status bytes to FIFO - we read from registers instead
this->state_.APPEND_STATUS = 0;
} else {
// Configure GDO0 for serial data (async serial mode)
this->state_.GDO0_CFG = 0x0D;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::PKTCTRL1);
this->write_(Register::IOCFG0);
this->write_(Register::FIFOTHR);
}

View File

@@ -65,8 +65,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE);
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
// Add newline before writing to console
this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer, buffer_at);
}

View File

@@ -117,17 +117,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
// "0x" + 2 hex digits per byte + '\0'
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
// Platform-specific: does write_msg_ add its own newline?
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, RP2040, LibreTiny)
// Allows single write call with newline included for efficiency
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
// Newline should NOT be added to buffer
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
#else
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
@@ -259,22 +248,20 @@ class Logger : public Component {
}
}
// Helper to add newline to buffer for platforms that need it
// Helper to add newline to buffer before writing to console
// Modifies buffer_at to include the newline
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
// Add newline - don't need to maintain null termination
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
// Add newline - don't need to maintain null termination
// write_msg_ receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
}
@@ -283,7 +270,7 @@ class Logger : public Component {
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
if (this->baud_rate_ > 0) {
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
}
}

View File

@@ -3,16 +3,23 @@
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg, size_t) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];
void HOT Logger::write_msg_(const char *msg, size_t len) {
static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]"
// tx_buffer_size_ defaults to 512, so 768 covers default + headroom
char buffer[TIMESTAMP_LEN + 768];
time_t rawtime;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
fputs(buffer, stdout);
puts(msg);
struct tm *timeinfo = localtime(&rawtime);
size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo);
// Copy message (with newline already included by caller)
size_t copy_len = std::min(len, sizeof(buffer) - pos);
memcpy(buffer + pos, msg, copy_len);
pos += copy_len;
// Single write for everything
fwrite(buffer, 1, pos, stdout);
}
void Logger::pre_setup() { global_logger = this; }

View File

@@ -6,6 +6,7 @@
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#include <zephyr/usb/usb_device.h>
namespace esphome::logger {
@@ -14,7 +15,7 @@ static const char *const TAG = "logger";
#ifdef USE_LOGGER_USB_CDC
void Logger::loop() {
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) {
return;
}
static bool opened = false;
@@ -62,18 +63,17 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t) {
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Single write with newline already in buffer (added by caller)
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
k_str_out(const_cast<char *>(msg), len);
#endif
if (nullptr == this->uart_dev_) {
if (this->uart_dev_ == nullptr) {
return;
}
while (*msg) {
uart_poll_out(this->uart_dev_, *msg);
++msg;
for (size_t i = 0; i < len; ++i) {
uart_poll_out(this->uart_dev_, msg[i]);
}
uart_poll_out(this->uart_dev_, '\n');
}
const LogString *Logger::get_uart_selection_() {

View File

@@ -162,14 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
float min_value = this->supports_cool_() ? -1.0f : 0.0f;
float max_value = this->supports_heat_() ? 1.0f : 0.0f;
this->autotuner_->config(min_value, max_value);
this->autotuner_->set_autotuner_id(this->get_object_id());
this->autotuner_->set_autotuner_id(this->get_name());
ESP_LOGI(TAG,
"%s: Autotune has started. This can take a long time depending on the "
"responsiveness of your system. Your system "
"output will be altered to deliberately oscillate above and below the setpoint multiple times. "
"Until your sensor provides a reading, the autotuner may display \'nan\'",
this->get_object_id().c_str());
this->get_name().c_str());
this->set_interval("autotune-progress", 10000, [this]() {
if (this->autotuner_ != nullptr && !this->autotuner_->is_finished())
@@ -178,7 +178,7 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!",
this->get_object_id().c_str());
this->get_name().c_str());
}
}

View File

@@ -112,7 +112,11 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
std::string PrometheusHandler::relabel_id_(EntityBase *obj) {
auto item = relabel_map_id_.find(obj);
return item == relabel_map_id_.end() ? obj->get_object_id() : item->second;
if (item != relabel_map_id_.end()) {
return item->second;
}
char object_id_buf[OBJECT_ID_MAX_LEN];
return obj->get_object_id_to(object_id_buf).str();
}
std::string PrometheusHandler::relabel_name_(EntityBase *obj) {

View File

@@ -0,0 +1,90 @@
import esphome.codegen as cg
from esphome.components import event, uart
import esphome.config_validation as cv
from esphome.const import CONF_EVENT_TYPES, CONF_ID
from esphome.core import ID
from esphome.types import ConfigType
from .. import uart_ns
CODEOWNERS = ["@eoasmxd"]
DEPENDENCIES = ["uart"]
UARTEvent = uart_ns.class_("UARTEvent", event.Event, uart.UARTDevice, cg.Component)
def validate_event_types(value) -> list[tuple[str, str | list[int]]]:
if not isinstance(value, list):
raise cv.Invalid("Event type must be a list of key-value mappings.")
processed: list[tuple[str, str | list[int]]] = []
for item in value:
if not isinstance(item, dict):
raise cv.Invalid(f"Event type item must be a mapping (dictionary): {item}")
if len(item) != 1:
raise cv.Invalid(
f"Event type item must be a single key-value mapping: {item}"
)
# Get the single key-value pair
event_name, match_data = next(iter(item.items()))
if not isinstance(event_name, str):
raise cv.Invalid(f"Event name (key) must be a string: {event_name}")
try:
# Try to validate as list of hex bytes
match_data_bin = cv.ensure_list(cv.hex_uint8_t)(match_data)
processed.append((event_name, match_data_bin))
continue
except cv.Invalid:
pass # Not binary, try string
try:
# Try to validate as string
match_data_str = cv.string_strict(match_data)
processed.append((event_name, match_data_str))
continue
except cv.Invalid:
pass # Not string either
# If neither validation passed
raise cv.Invalid(
f"Event match data for '{event_name}' must be a string or a list of hex bytes. Invalid data: {match_data}"
)
if not processed:
raise cv.Invalid("event_types must contain at least one event mapping.")
return processed
CONFIG_SCHEMA = (
event.event_schema(UARTEvent)
.extend(
{
cv.Required(CONF_EVENT_TYPES): validate_event_types,
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config: ConfigType) -> None:
event_names = [item[0] for item in config[CONF_EVENT_TYPES]]
var = await event.new_event(config, event_types=event_names)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for i, (event_name, match_data) in enumerate(config[CONF_EVENT_TYPES]):
if isinstance(match_data, str):
match_data = [ord(c) for c in match_data]
match_data_var_id = ID(
f"match_data_{config[CONF_ID]}_{i}", is_declaration=True, type=cg.uint8
)
match_data_var = cg.static_const_array(
match_data_var_id, cg.ArrayInitializer(*match_data)
)
cg.add(var.add_event_matcher(event_name, match_data_var, len(match_data)))

View File

@@ -0,0 +1,48 @@
#include "uart_event.h"
#include "esphome/core/log.h"
#include <algorithm>
namespace esphome::uart {
static const char *const TAG = "uart.event";
void UARTEvent::setup() {}
void UARTEvent::dump_config() { LOG_EVENT("", "UART Event", this); }
void UARTEvent::loop() { this->read_data_(); }
void UARTEvent::add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len) {
this->matchers_.push_back({event_name, match_data, match_data_len});
if (match_data_len > this->max_matcher_len_) {
this->max_matcher_len_ = match_data_len;
}
}
void UARTEvent::read_data_() {
while (this->available()) {
uint8_t data;
this->read_byte(&data);
this->buffer_.push_back(data);
bool match_found = false;
for (const auto &matcher : this->matchers_) {
if (this->buffer_.size() < matcher.data_len) {
continue;
}
if (std::equal(matcher.data, matcher.data + matcher.data_len, this->buffer_.end() - matcher.data_len)) {
this->trigger(matcher.event_name);
this->buffer_.clear();
match_found = true;
break;
}
}
if (!match_found && this->max_matcher_len_ > 0 && this->buffer_.size() > this->max_matcher_len_) {
this->buffer_.erase(this->buffer_.begin());
}
}
}
} // namespace esphome::uart

View File

@@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/event/event.h"
#include "esphome/components/uart/uart.h"
#include <vector>
namespace esphome::uart {
class UARTEvent : public event::Event, public UARTDevice, public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
void add_event_matcher(const char *event_name, const uint8_t *match_data, size_t match_data_len);
protected:
struct EventMatcher {
const char *event_name;
const uint8_t *data;
size_t data_len;
};
void read_data_();
std::vector<EventMatcher> matchers_;
std::vector<uint8_t> buffer_;
size_t max_matcher_len_ = 0;
};
} // namespace esphome::uart

View File

@@ -627,9 +627,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
ESP_LOGD(TAG, "Assist Pipeline running");
#ifdef USE_MEDIA_PLAYER
this->started_streaming_tts_ = false;
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "url") {
this->tts_response_url_ = std::move(arg.value);
this->tts_response_url_ = arg.value;
}
}
#endif
@@ -648,9 +648,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
break;
case api::enums::VOICE_ASSISTANT_STT_END: {
std::string text;
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "text") {
text = std::move(arg.value);
text = arg.value;
}
}
if (text.empty()) {
@@ -693,9 +693,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
break;
}
case api::enums::VOICE_ASSISTANT_INTENT_END: {
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "conversation_id") {
this->conversation_id_ = std::move(arg.value);
this->conversation_id_ = arg.value;
} else if (arg.name == "continue_conversation") {
this->continue_conversation_ = (arg.value == "1");
}
@@ -705,9 +705,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
}
case api::enums::VOICE_ASSISTANT_TTS_START: {
std::string text;
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "text") {
text = std::move(arg.value);
text = arg.value;
}
}
if (text.empty()) {
@@ -731,9 +731,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
}
case api::enums::VOICE_ASSISTANT_TTS_END: {
std::string url;
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "url") {
url = std::move(arg.value);
url = arg.value;
}
}
if (url.empty()) {
@@ -778,11 +778,11 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
case api::enums::VOICE_ASSISTANT_ERROR: {
std::string code = "";
std::string message = "";
for (auto arg : msg.data) {
for (const auto &arg : msg.data) {
if (arg.name == "code") {
code = std::move(arg.value);
code = arg.value;
} else if (arg.name == "message") {
message = std::move(arg.value);
message = arg.value;
}
}
if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") {

View File

@@ -33,6 +33,15 @@ class EntityBase {
bool has_own_name() const { return this->flags_.has_own_name; }
// Get the sanitized name of this Entity as an ID.
// Deprecated: object_id mangles names and all object_id methods are planned for removal.
// See https://github.com/esphome/backlog/issues/76
// Now is the time to stop using object_id entirely. If you still need it temporarily,
// use get_object_id_to() which will remain available longer but will also eventually be removed.
ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal "
"(see https://github.com/esphome/backlog/issues/76). "
"Now is the time to stop using object_id. If still needed, use get_object_id_to() "
"which will remain available longer. get_object_id() will be removed in 2026.7.0",
"2025.12.0")
std::string get_object_id() const;
void set_object_id(const char *object_id);

View File

@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.1.0
click==8.1.7
esphome-dashboard==20251013.0
aioesphomeapi==43.5.0
aioesphomeapi==43.6.0
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.18.17 # dashboard_import

View File

@@ -374,20 +374,16 @@ def create_field_type_info(
# Traditional fixed array approach with copy
return FixedArrayBytesType(field, fixed_size)
# Check for pointer_to_buffer option on string fields
if field.type == 9:
has_pointer_to_buffer = get_field_opt(field, pb.pointer_to_buffer, False)
if has_pointer_to_buffer:
# Zero-copy pointer approach for strings
return PointerToBytesBufferType(field, None)
# Special handling for bytes fields
if field.type == 12:
return BytesType(field, needs_decode, needs_encode)
# Special handling for string fields
if field.type == 9:
# For SOURCE_CLIENT only messages (decode but no encode), use StringRef
# for zero-copy access to the receive buffer
if needs_decode and not needs_encode:
return PointerToStringBufferType(field, None)
return StringType(field, needs_decode, needs_encode)
validate_field_type(field.type, field.name)
@@ -840,8 +836,8 @@ class BytesType(TypeInfo):
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
class PointerToBytesBufferType(TypeInfo):
"""Type for bytes fields that use pointer_to_buffer option for zero-copy."""
class PointerToBufferTypeBase(TypeInfo):
"""Base class for pointer_to_buffer types (bytes and strings) for zero-copy decoding."""
@classmethod
def can_use_dump_field(cls) -> bool:
@@ -851,29 +847,34 @@ class PointerToBytesBufferType(TypeInfo):
self, field: descriptor.FieldDescriptorProto, size: int | None = None
) -> None:
super().__init__(field)
# Size is not used for pointer_to_buffer - we always use size_t for length
self.array_size = 0
@property
def cpp_type(self) -> str:
return "const uint8_t*"
def decode_length(self) -> str | None:
# This is handled in decode_length_content
return None
@property
def default_value(self) -> str:
return "nullptr"
def wire_type(self) -> WireType:
"""Get the wire type for this field."""
return WireType.LENGTH_DELIMITED # Uses wire type 2
@property
def reference_type(self) -> str:
return "const uint8_t*"
def get_estimated_size(self) -> int:
# field ID + length varint + typical data (assume small for pointer fields)
return self.calculate_field_id_size() + 2 + 16
@property
def const_reference_type(self) -> str:
return "const uint8_t*"
class PointerToBytesBufferType(PointerToBufferTypeBase):
"""Type for bytes fields that use pointer_to_buffer option for zero-copy."""
cpp_type = "const uint8_t*"
default_value = "nullptr"
reference_type = "const uint8_t*"
const_reference_type = "const uint8_t*"
@property
def public_content(self) -> list[str]:
# Use uint16_t for length - max packet size is well below 65535
# Add pointer and length fields
return [
f"const uint8_t* {self.field_name}{{nullptr}};",
f"uint16_t {self.field_name}_len{{0}};",
@@ -885,24 +886,12 @@ class PointerToBytesBufferType(TypeInfo):
@property
def decode_length_content(self) -> str | None:
# Decode directly stores the pointer to avoid allocation
return f"""case {self.number}: {{
// Use raw data directly to avoid allocation
this->{self.field_name} = value.data();
this->{self.field_name}_len = value.size();
break;
}}"""
@property
def decode_length(self) -> str | None:
# This is handled in decode_length_content
return None
@property
def wire_type(self) -> WireType:
"""Get the wire type for this bytes field."""
return WireType.LENGTH_DELIMITED # Uses wire type 2
def dump(self, name: str) -> str:
return (
f"format_hex_pretty(this->{self.field_name}, this->{self.field_name}_len)"
@@ -910,7 +899,6 @@ class PointerToBytesBufferType(TypeInfo):
@property
def dump_content(self) -> str:
# Custom dump that doesn't use dump_field template
return (
f'out.append(" {self.name}: ");\n'
+ f"out.append({self.dump(self.field_name)});\n"
@@ -918,11 +906,48 @@ class PointerToBytesBufferType(TypeInfo):
)
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"size.add_length({self.number}, this->{self.field_name}_len);"
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len);"
def get_estimated_size(self) -> int:
# field ID + length varint + typical data (assume small for pointer fields)
return self.calculate_field_id_size() + 2 + 16
class PointerToStringBufferType(PointerToBufferTypeBase):
"""Type for string fields that use pointer_to_buffer option for zero-copy.
Uses StringRef instead of separate pointer and length fields.
"""
cpp_type = "StringRef"
default_value = ""
reference_type = "StringRef &"
const_reference_type = "const StringRef &"
@property
def public_content(self) -> list[str]:
return [f"StringRef {self.field_name}{{}};"]
@property
def encode_content(self) -> str:
return f"buffer.encode_string({self.number}, this->{self.field_name});"
@property
def decode_length_content(self) -> str | None:
return f"""case {self.number}: {{
this->{self.field_name} = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
break;
}}"""
def dump(self, name: str) -> str:
return f'out.append("\'").append(this->{self.field_name}.c_str(), this->{self.field_name}.size()).append("\'");'
@property
def dump_content(self) -> str:
return (
f'out.append(" {self.name}: ");\n'
+ f"{self.dump(self.field_name)}\n"
+ 'out.append("\\n");'
)
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}.size());"
class FixedArrayBytesType(TypeInfo):

View File

@@ -75,3 +75,11 @@ button:
- uart.write: !lambda |-
std::string cmd = "VALUE=" + str_sprintf("%.0f", id(test_number).state) + "\r\n";
return std::vector<uint8_t>(cmd.begin(), cmd.end());
event:
- platform: uart
uart_id: uart_uart
name: "UART Event"
event_types:
- "string_event_A": "*A#"
- "bytes_event_B": [0x2A, 0x42, 0x23]

View File

@@ -31,3 +31,11 @@ button:
name: "UART Button"
uart_id: uart_uart
data: [0xFF, 0xEE]
event:
- platform: uart
uart_id: uart_uart
name: "UART Event"
event_types:
- "string_event_A": "*A#"
- "bytes_event_B": [0x2A, 0x42, 0x23]