Merge branch 'dev' into wifi_roam

This commit is contained in:
J. Nick Koston
2026-01-02 14:28:59 -10:00
committed by GitHub
115 changed files with 6300 additions and 5512 deletions
+3 -1
View File
@@ -30,7 +30,9 @@ void A01nyubComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
+3 -1
View File
@@ -29,7 +29,9 @@ void A02yyuwComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %f mm", distance);
this->publish_state(distance);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
+17 -17
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"];
}
@@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest {
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_NOISE";
bytes key = 1 [(pointer_to_buffer) = true];
bytes key = 1;
}
message NoiseEncryptionSetKeyResponse {
@@ -796,7 +796,7 @@ message HomeassistantActionResponse {
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
bool success = 2; // Whether the service call succeeded
string error_message = 3; // Error message if success = false
bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
}
// ==================== IMPORT HOME ASSISTANT STATES ====================
@@ -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"];
}
@@ -1292,7 +1292,7 @@ message ListEntitiesSirenResponse {
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 6;
repeated string tones = 7;
repeated string tones = 7 [(container_pointer_no_template) = "FixedVector<const char *>"];
bool supports_duration = 8;
bool supports_volume = 9;
EntityCategory entity_category = 10;
@@ -1692,7 +1692,7 @@ message BluetoothGATTWriteRequest {
uint32 handle = 2;
bool response = 3;
bytes data = 4 [(pointer_to_buffer) = true];
bytes data = 4;
}
message BluetoothGATTReadDescriptorRequest {
@@ -1712,7 +1712,7 @@ message BluetoothGATTWriteDescriptorRequest {
uint64 address = 1;
uint32 handle = 2;
bytes data = 3 [(pointer_to_buffer) = true];
bytes data = 3;
}
message BluetoothGATTNotifyRequest {
@@ -1937,7 +1937,7 @@ message VoiceAssistantAudio {
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_VOICE_ASSISTANT";
bytes data = 1;
bytes data = 1 [(pointer_to_buffer) = true];
bool end = 2;
}
+20 -20
View File
@@ -473,7 +473,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
@@ -559,7 +559,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
@@ -738,11 +738,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();
@@ -931,7 +931,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
@@ -1153,9 +1153,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
}
@@ -1522,7 +1521,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_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
@@ -1550,7 +1549,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_();
}
@@ -1693,27 +1692,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);
}
}
@@ -1749,20 +1749,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
// the action list. This ensures async actions (delays, waits) complete first.
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) {
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.set_error_message(error_message);
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
ExecuteServiceResponse resp;
resp.call_id = call_id;
resp.success = success;
resp.set_error_message(StringRef(error_message));
resp.set_error_message(error_message);
resp.response_data = response_data;
resp.response_data_len = response_data_len;
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
+2 -2
View File
@@ -233,9 +233,9 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_API_USER_DEFINED_ACTIONS
void execute_service(const ExecuteServiceRequest &msg) override;
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message);
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
+81 -88
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:
@@ -1732,8 +1710,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon_ref_);
#endif
buffer.encode_bool(6, this->disabled_by_default);
for (auto &it : this->tones) {
buffer.encode_string(7, it, true);
for (const char *it : *this->tones) {
buffer.encode_string(7, it, strlen(it), true);
}
buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume);
@@ -1750,9 +1728,9 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->icon_ref_.size());
#endif
size.add_bool(1, this->disabled_by_default);
if (!this->tones.empty()) {
for (const auto &it : this->tones) {
size.add_length_force(1, it.size());
if (!this->tones->empty()) {
for (const char *it : *this->tones) {
size.add_length_force(1, strlen(it));
}
}
size.add_bool(1, this->supports_duration);
@@ -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;
}
@@ -2546,20 +2527,22 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1:
this->data = value.as_string();
case 1: {
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bytes(1, this->data_ptr_, this->data_len_);
buffer.encode_bytes(1, this->data, this->data_len);
buffer.encode_bool(2, this->end);
}
void VoiceAssistantAudio::calculate_size(ProtoSize &size) const {
size.add_length(1, this->data_len_);
size.add_length(1, this->data_len);
size.add_bool(1, this->end);
}
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@@ -2583,12 +2566,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 +2591,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 +2638,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 +2770,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 +2855,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 +3326,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 +3350,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 +3365,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
+46 -62
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
@@ -1073,7 +1069,7 @@ class SubscribeLogsResponse final : public ProtoMessage {
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 124;
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 "noise_encryption_set_key_request"; }
#endif
@@ -1165,13 +1161,13 @@ class HomeassistantActionRequest final : public ProtoMessage {
class HomeassistantActionResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 130;
static constexpr uint8_t ESTIMATED_SIZE = 34;
static constexpr uint8_t ESTIMATED_SIZE = 24;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_action_response"; }
#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
@@ -1719,7 +1708,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_siren_response"; }
#endif
std::vector<std::string> tones{};
const FixedVector<const char *> *tones{};
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -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
@@ -2157,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 75;
static constexpr uint8_t ESTIMATED_SIZE = 29;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_request"; }
#endif
@@ -2193,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 77;
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 "bluetooth_gatt_write_descriptor_request"; }
#endif
@@ -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
@@ -2532,17 +2521,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
class VoiceAssistantAudio final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 106;
static constexpr uint8_t ESTIMATED_SIZE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "voice_assistant_audio"; }
#endif
std::string data{};
const uint8_t *data_ptr_{nullptr};
size_t data_len_{0};
void set_data(const uint8_t *data, size_t len) {
this->data_ptr_ = data;
this->data_len_ = len;
}
const uint8_t *data{nullptr};
uint16_t data_len{0};
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
@@ -2562,8 +2546,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 +2566,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 +2611,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 +2718,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 +2775,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
+70 -36
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);
@@ -1575,7 +1579,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_);
#endif
dump_field(out, "disabled_by_default", this->disabled_by_default);
for (const auto &it : this->tones) {
for (const auto &it : *this->tones) {
dump_field(out, "tones", it, 4);
}
dump_field(out, "supports_duration", this->supports_duration);
@@ -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");
@@ -1964,28 +1978,34 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
void VoiceAssistantAudio::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "VoiceAssistantAudio");
out.append(" data: ");
if (this->data_ptr_ != nullptr) {
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
} else {
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
}
out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n");
dump_field(out, "end", this->end);
}
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 +2019,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 +2096,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 +2135,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
+4 -4
View File
@@ -394,7 +394,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon
this->action_response_callbacks_.push_back({call_id, std::move(callback)});
}
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
auto callback = std::move(it->callback);
@@ -406,7 +406,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
}
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
if (it->call_id == call_id) {
@@ -678,7 +678,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
}
}
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {
call.connection->send_execute_service_response(call.client_call_id, success, error_message);
@@ -688,7 +688,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
}
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len) {
for (auto &call : this->active_action_calls_) {
if (call.action_call_id == action_call_id) {
+5 -5
View File
@@ -143,10 +143,10 @@ class APIServer : public Component,
// Action response handling
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
const uint8_t *response_data, size_t response_data_len);
void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
size_t response_data_len);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
@@ -165,9 +165,9 @@ class APIServer : public Component,
void unregister_active_action_call(uint32_t action_call_id);
void unregister_active_action_calls_for_connection(APIConnection *conn);
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message);
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
const uint8_t *response_data, size_t response_data_len);
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
@@ -67,10 +67,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
// the callback is invoked synchronously while the message is on the stack).
class ActionResponse {
public:
ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {}
ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
: success_(success), error_message_(error_message) {
if (data == nullptr || data_len == 0)
return;
+3 -3
View File
@@ -255,7 +255,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
bool return_response = std::get<1>(args);
if (!return_response) {
// Client doesn't want response data, just send success/error
this->parent_->send_action_response(call_id, success, error_message);
this->parent_->send_action_response(call_id, success, StringRef(error_message));
return;
}
}
@@ -265,12 +265,12 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
json::JsonBuilder builder;
this->json_builder_(x..., builder.root());
std::string json_str = builder.serialize();
this->parent_->send_action_response(call_id, success, error_message,
this->parent_->send_action_response(call_id, success, StringRef(error_message),
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
return;
}
#endif
this->parent_->send_action_response(call_id, success, error_message);
this->parent_->send_action_response(call_id, success, StringRef(error_message));
}
protected:
+8 -1
View File
@@ -7,8 +7,12 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
namespace esphome::ble_client {
// placeholder class for static TAG .
@@ -151,7 +155,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
#endif
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
@@ -47,7 +47,10 @@ struct DNSAnswer {
void DNSServer::start(const network::IPAddress &ip) {
this->server_ip_ = ip;
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
#endif
// Create loop-monitored UDP socket
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
+5 -3
View File
@@ -1,11 +1,13 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cse7766 {
static const char *const TAG = "cse7766";
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
@@ -70,8 +72,8 @@ bool CSE7766Component::check_byte_() {
void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_));
ESP_LOGVV(TAG, "Raw data: %s", s.c_str());
char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)];
ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_)));
}
#endif
+7 -1
View File
@@ -7,6 +7,9 @@ namespace ee895 {
static const char *const TAG = "ee895";
// Serial number is 16 bytes
static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16;
static const uint16_t CRC16_ONEWIRE_START = 0xFFFF;
static const uint8_t FUNCTION_CODE_READ = 0x03;
static const uint16_t SERIAL_NUMBER = 0x0000;
@@ -26,7 +29,10 @@ void EE895Component::setup() {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char serial_hex[format_hex_size(EE895_SERIAL_NUMBER_SIZE)];
#endif
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex_to(serial_hex, serial_number + 2, EE895_SERIAL_NUMBER_SIZE));
}
void EE895Component::dump_config() {
+5 -1
View File
@@ -7,6 +7,7 @@
namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi";
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
@@ -68,8 +69,11 @@ void EPaperBase::data(uint8_t value) {
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
ESP_LOGV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length,
format_hex_pretty(ptr, length, '.', false).c_str());
format_hex_pretty_to(hex_buf, ptr, length, '.'));
#endif
this->dc_pin_->digital_write(false);
this->enable();
@@ -76,6 +76,12 @@ class EPaperBase : public Display,
return 0;
}
void fill(Color color) override {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
auto pixel_color = color_to_bit(color) ? 0xFF : 0x00;
// We store 8 pixels per byte
@@ -97,6 +97,12 @@ void EPaperSpectraE6::deep_sleep() {
}
void EPaperSpectraE6::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
EPaperBase::fill(color);
return;
}
auto pixel_color = color_to_hex(color);
// We store 2 pixels per byte
+39
View File
@@ -85,6 +85,7 @@ CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision"
CONF_RELEASE = "release"
LOG_LEVELS_IDF = [
@@ -109,6 +110,21 @@ COMPILER_OPTIMIZATIONS = {
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
}
# ESP32 (original) chip revision options
# Setting minimum revision to 3.0 or higher:
# - Reduces flash size by excluding workaround code for older chip bugs
# - For PSRAM users: disables CONFIG_SPIRAM_CACHE_WORKAROUND, which saves significant
# IRAM by keeping C library functions in ROM instead of recompiling them
# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/chip_revision.html
ESP32_CHIP_REVISIONS = {
"0.0": "CONFIG_ESP32_REV_MIN_0",
"1.0": "CONFIG_ESP32_REV_MIN_1",
"1.1": "CONFIG_ESP32_REV_MIN_1_1",
"2.0": "CONFIG_ESP32_REV_MIN_2",
"3.0": "CONFIG_ESP32_REV_MIN_3",
"3.1": "CONFIG_ESP32_REV_MIN_3_1",
}
# Socket limit configuration for ESP-IDF
# ESP-IDF CONFIG_LWIP_MAX_SOCKETS has range 1-253, default 10
DEFAULT_MAX_SOCKETS = 10 # ESP-IDF default
@@ -566,6 +582,16 @@ def final_validate(config):
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
)
)
if (
config[CONF_VARIANT] != VARIANT_ESP32
and advanced.get(CONF_MINIMUM_CHIP_REVISION) is not None
):
errs.append(
cv.Invalid(
f"'{CONF_MINIMUM_CHIP_REVISION}' is only supported on {VARIANT_ESP32}",
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION],
)
)
if advanced[CONF_EXECUTE_FROM_PSRAM]:
if config[CONF_VARIANT] != VARIANT_ESP32S3:
errs.append(
@@ -694,6 +720,9 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of(
*ESP32_CHIP_REVISIONS
),
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
# it will handle disabling DHCP server when AP is not configured.
# Default to false (disabled) when WiFi is not used.
@@ -1017,6 +1046,16 @@ async def to_code(config):
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
# Set minimum chip revision for ESP32 variant
# Setting this to 3.0 or higher reduces flash size by excluding workaround code,
# and for PSRAM users saves significant IRAM by keeping C library functions in ROM.
if variant == VARIANT_ESP32:
min_rev = conf[CONF_ADVANCED].get(CONF_MINIMUM_CHIP_REVISION)
if min_rev is not None:
for rev, flag in ESP32_CHIP_REVISIONS.items():
add_idf_sdkconfig_option(flag, rev == min_rev)
cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET")
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv")
@@ -37,6 +37,9 @@ namespace esphome::esp32_ble_tracker {
static const char *const TAG = "esp32_ble_tracker";
// BLE advertisement max: 31 bytes adv data + 31 bytes scan response
static constexpr size_t BLE_ADV_MAX_LOG_BYTES = 62;
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
const char *client_state_to_string(ClientState state) {
@@ -445,6 +448,7 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf);
}
char hex_buf[format_hex_pretty_size(BLE_ADV_MAX_LOG_BYTES)];
for (auto &data : this->manufacturer_datas_) {
auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
if (ibeacon.has_value()) {
@@ -458,7 +462,8 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
} else {
char uuid_buf[esp32_ble::UUID_STR_LEN];
data.uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, format_hex_pretty(data.data).c_str());
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf,
format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
}
for (auto &data : this->service_datas_) {
@@ -466,11 +471,11 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
char uuid_buf[esp32_ble::UUID_STR_LEN];
data.uuid.to_str(uuid_buf);
ESP_LOGVV(TAG, " UUID: %s", uuid_buf);
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
ESP_LOGVV(TAG, " Adv data: %s",
format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str());
format_hex_pretty_to(hex_buf, scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len));
#endif
}
@@ -4,6 +4,7 @@
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble_server/ble_2902.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -14,6 +15,7 @@ namespace esp32_improv {
using namespace bytebuffer;
static const char *const TAG = "esp32_improv.component";
static constexpr size_t IMPROV_MAX_LOG_BYTES = 128;
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
static constexpr uint16_t STOP_ADVERTISING_DELAY =
10000; // Delay (ms) before stopping service to allow BLE clients to read the final state
@@ -314,7 +316,11 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1];
ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(IMPROV_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "Processing bytes - %s",
format_hex_pretty_to(hex_buf, this->incoming_data_.data(), this->incoming_data_.size()));
#endif
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
@@ -403,8 +409,12 @@ void ESP32ImprovComponent::check_wifi_connection_() {
#ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) {
char url_buffer[64];
snprintf(url_buffer, sizeof(url_buffer), "http://%s:%d", ip.str().c_str(), USE_WEBSERVER_PORT);
// "http://" (7) + IPv4 max (15) + ":" (1) + port max (5) + null = 29
char url_buffer[32];
memcpy(url_buffer, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates
ip.str_to(url_buffer + 7);
size_t len = strlen(url_buffer);
snprintf(url_buffer + len, sizeof(url_buffer) - len, ":%d", USE_WEBSERVER_PORT);
url_strings[url_count++] = url_buffer;
break;
}
+2 -17
View File
@@ -16,7 +16,7 @@ from esphome.const import (
CONF_SAFE_MODE,
CONF_VERSION,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import coroutine_with_priority
from esphome.coroutine import CoroPriority
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -28,17 +28,7 @@ CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
def supports_sha256() -> bool:
"""Check if the current platform supports SHA256 for OTA authentication."""
return bool(CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny)
def AUTO_LOAD() -> list[str]:
"""Conditionally auto-load sha256 only on platforms that support it."""
base_components = ["md5", "socket"]
if supports_sha256():
return base_components + ["sha256"]
return base_components
AUTO_LOAD = ["sha256", "socket"]
esphome = cg.esphome_ns.namespace("esphome")
@@ -155,11 +145,6 @@ async def to_code(config: ConfigType) -> None:
if config.get(CONF_PASSWORD):
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
cg.add_define("USE_OTA_PASSWORD")
# Only include hash algorithms when password is configured
cg.add_define("USE_OTA_MD5")
# Only include SHA256 support on platforms that have it
if supports_sha256():
cg.add_define("USE_OTA_SHA256")
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
await cg.register_component(var, config)
+21 -129
View File
@@ -1,13 +1,8 @@
#include "ota_esphome.h"
#ifdef USE_OTA
#ifdef USE_OTA_PASSWORD
#ifdef USE_OTA_MD5
#include "esphome/components/md5/md5.h"
#endif
#ifdef USE_OTA_SHA256
#include "esphome/components/sha256/sha256.h"
#endif
#endif
#include "esphome/components/network/util.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_esp8266.h"
@@ -31,15 +26,6 @@ static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
#ifdef USE_OTA_PASSWORD
#ifdef USE_OTA_MD5
static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
#endif
#ifdef USE_OTA_SHA256
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
#endif
#endif // USE_OTA_PASSWORD
void ESPHomeOTAComponent::setup() {
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->server_ == nullptr) {
@@ -108,15 +94,7 @@ void ESPHomeOTAComponent::loop() {
}
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
#ifdef USE_OTA_SHA256
static const uint8_t FEATURE_SUPPORTS_SHA256_AUTH = 0x02;
#endif
// Temporary flag to allow MD5 downgrade for ~3 versions (until 2026.1.0)
// This allows users to downgrade via OTA if they encounter issues after updating.
// Without this, users would need to do a serial flash to downgrade.
// TODO: Remove this flag and all associated code in 2026.1.0
#define ALLOW_OTA_DOWNGRADE_MD5
void ESPHomeOTAComponent::handle_handshake_() {
/// Handle the OTA handshake and authentication.
@@ -547,26 +525,8 @@ void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
void ESPHomeOTAComponent::log_auth_warning_(const LogString *msg) { ESP_LOGW(TAG, "Auth: %s", LOG_STR_ARG(msg)); }
bool ESPHomeOTAComponent::select_auth_type_() {
#ifdef USE_OTA_SHA256
bool client_supports_sha256 = (this->ota_features_ & FEATURE_SUPPORTS_SHA256_AUTH) != 0;
#ifdef ALLOW_OTA_DOWNGRADE_MD5
// Allow fallback to MD5 if client doesn't support SHA256
if (client_supports_sha256) {
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
return true;
}
#ifdef USE_OTA_MD5
this->log_auth_warning_(LOG_STR("Using deprecated MD5"));
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
return true;
#else
this->log_auth_warning_(LOG_STR("SHA256 required"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
#endif // USE_OTA_MD5
#else // !ALLOW_OTA_DOWNGRADE_MD5
// Require SHA256
if (!client_supports_sha256) {
this->log_auth_warning_(LOG_STR("SHA256 required"));
@@ -575,20 +535,6 @@ bool ESPHomeOTAComponent::select_auth_type_() {
}
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_SHA256_AUTH;
return true;
#endif // ALLOW_OTA_DOWNGRADE_MD5
#else // !USE_OTA_SHA256
#ifdef USE_OTA_MD5
// Only MD5 available
this->auth_type_ = ota::OTA_RESPONSE_REQUEST_AUTH;
return true;
#else
// No auth methods available
this->log_auth_warning_(LOG_STR("No auth methods available"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID);
return false;
#endif // USE_OTA_MD5
#endif // USE_OTA_SHA256
}
bool ESPHomeOTAComponent::handle_auth_send_() {
@@ -612,31 +558,12 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
hasher = &sha_hasher;
}
#endif
#ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
hasher = &md5_hasher;
}
#endif
const size_t hex_size = hasher->get_size() * 2;
const size_t nonce_len = hasher->get_size() / 4;
const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4;
const size_t auth_buf_size = 1 + 3 * hex_size;
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
this->auth_buf_pos_ = 0;
@@ -648,17 +575,17 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
return false;
}
hasher->init();
hasher->add(buf, nonce_len);
hasher->calculate();
hasher.init();
hasher.add(buf, nonce_len);
hasher.calculate();
this->auth_buf_[0] = this->auth_type_;
hasher->get_hex(buf);
hasher.get_hex(buf);
ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf);
}
// Try to write auth_type + nonce
size_t hex_size = this->get_auth_hex_size_();
constexpr size_t hex_size = SHA256_HEX_SIZE;
const size_t to_write = 1 + hex_size;
size_t remaining = to_write - this->auth_buf_pos_;
@@ -680,7 +607,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
}
bool ESPHomeOTAComponent::handle_auth_read_() {
size_t hex_size = this->get_auth_hex_size_();
constexpr size_t hex_size = SHA256_HEX_SIZE;
const size_t to_read = hex_size * 2; // CNonce + Response
// Try to read remaining bytes (CNonce + Response)
@@ -705,45 +632,25 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
const char *cnonce = nonce + hex_size;
const char *response = cnonce + hex_size;
// CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions).
// Declare both hash objects in same stack frame, use pointer to select.
// NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3
// hardware SHA acceleration - the object must exist in this stack frame for all operations.
// Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope.
#ifdef USE_OTA_SHA256
sha256::SHA256 sha_hasher;
#endif
#ifdef USE_OTA_MD5
md5::MD5Digest md5_hasher;
#endif
HashBase *hasher = nullptr;
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
hasher = &sha_hasher;
}
#endif
#ifdef USE_OTA_MD5
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) {
hasher = &md5_hasher;
}
#endif
hasher->init();
hasher->add(this->password_.c_str(), this->password_.length());
hasher->add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher->calculate();
hasher.init();
hasher.add(this->password_.c_str(), this->password_.length());
hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
hasher.calculate();
ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char computed_hash[65]; // Buffer for hex-encoded hash (max expected length + null terminator)
hasher->get_hex(computed_hash);
char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
hasher.get_hex(computed_hash);
ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash);
#endif
ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response);
// Compare response
bool matches = hasher->equals_hex(response);
bool matches = hasher.equals_hex(response);
if (!matches) {
this->log_auth_warning_(LOG_STR("Password mismatch"));
@@ -757,21 +664,6 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
return true;
}
size_t ESPHomeOTAComponent::get_auth_hex_size_() const {
#ifdef USE_OTA_SHA256
if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) {
return SHA256_HEX_SIZE;
}
#endif
#ifdef USE_OTA_MD5
return MD5_HEX_SIZE;
#else
#ifndef USE_OTA_SHA256
#error "Either USE_OTA_MD5 or USE_OTA_SHA256 must be defined when USE_OTA_PASSWORD is enabled"
#endif
#endif
}
void ESPHomeOTAComponent::cleanup_auth_() {
this->auth_buf_ = nullptr;
this->auth_buf_pos_ = 0;
+1 -1
View File
@@ -44,10 +44,10 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
void handle_handshake_();
void handle_data_();
#ifdef USE_OTA_PASSWORD
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
bool handle_auth_send_();
bool handle_auth_read_();
bool select_auth_type_();
size_t get_auth_hex_size_() const;
void cleanup_auth_();
void log_auth_warning_(const LogString *msg);
#endif // USE_OTA_PASSWORD
@@ -6,6 +6,7 @@
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <esp_event.h>
@@ -299,9 +300,10 @@ void ESPNowComponent::loop() {
// Intentionally left as if instead of else in case the peer is added above
if (esp_now_is_peer_exist(info.src_addr)) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(),
format_mac_address_pretty(info.des_addr).c_str(),
format_hex_pretty(packet->packet_.receive.data, packet->packet_.receive.size).c_str());
format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
#endif
if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
for (auto *handler : this->broadcasted_handlers_) {
@@ -1,5 +1,6 @@
#include "ethernet_component.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@@ -39,6 +40,9 @@ namespace ethernet {
static const char *const TAG = "ethernet";
// PHY register size for hex logging
static constexpr size_t PHY_REG_SIZE = 2;
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
@@ -773,7 +777,10 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
uint32_t phy_control_2;
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
#endif
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
@@ -790,7 +797,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
}
}
#endif // USE_ETHERNET_KSZ8081
+7 -1
View File
@@ -8,6 +8,9 @@ namespace esphome::hlk_fm22x {
static const char *const TAG = "hlk_fm22x";
// Maximum response size is 36 bytes (VERIFY reply: face_id + 32-byte name)
static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36;
void HlkFm22xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
this->set_enrolling_(false);
@@ -142,7 +145,10 @@ void HlkFm22xComponent::recv_command_() {
data.push_back(byte);
}
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty_to(hex_buf, data.data(), data.size()));
#endif
byte = this->read();
if (byte != checksum) {
+7 -1
View File
@@ -12,6 +12,9 @@ namespace i2c {
static const char *const TAG = "i2c.arduino";
// Maximum bytes to log in hex format (truncates larger transfers)
static constexpr size_t I2C_MAX_LOG_BYTES = 32;
void ArduinoI2CBus::setup() {
recover_();
@@ -107,7 +110,10 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe
return ERROR_NOT_INITIALIZED;
}
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
uint8_t status = 0;
if (write_count != 0 || read_count == 0) {
+7 -1
View File
@@ -15,6 +15,9 @@ namespace i2c {
static const char *const TAG = "i2c.idf";
// Maximum bytes to log in hex format (truncates larger transfers)
static constexpr size_t I2C_MAX_LOG_BYTES = 32;
void IDFI2CBus::setup() {
static i2c_port_t next_hp_port = I2C_NUM_0;
#if SOC_LP_I2C_SUPPORTED
@@ -147,7 +150,10 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
jobs[num_jobs++].write.total_bytes = 1;
} else {
if (write_count != 0) {
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
@@ -131,6 +131,13 @@ float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWA
void ILI9XXXDisplay::fill(Color color) {
if (!this->check_buffer_())
return;
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint16_t new_color = 0;
this->x_low_ = 0;
this->y_low_ = 0;
+7
View File
@@ -293,6 +293,13 @@ void Inkplate::fill(Color color) {
ESP_LOGV(TAG, "Fill called");
uint32_t start_time = millis();
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
return;
}
if (this->greyscale_) {
uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5;
memset(this->buffer_, (fill << 4) | fill, this->get_buffer_length_());
+3 -1
View File
@@ -39,7 +39,9 @@ void Jsnsr04tComponent::check_buffer_() {
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
char hex_buf[format_hex_pretty_size(4)];
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
}
} else {
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
+10 -3
View File
@@ -413,7 +413,8 @@ bool LD2410Component::handle_ack_data_() {
return true;
}
if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -597,11 +598,17 @@ void LD2410Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
+10 -3
View File
@@ -457,7 +457,8 @@ bool LD2412Component::handle_ack_data_() {
return true;
}
if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -670,11 +671,17 @@ void LD2412Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
+10 -3
View File
@@ -607,7 +607,8 @@ bool LD2450Component::handle_ack_data_() {
return true;
}
if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
@@ -758,11 +759,17 @@ void LD2450Component::readline_(int readch) {
}
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame
} else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
@@ -36,7 +36,7 @@ static const char *get_color_mode_json_str(ColorMode mode) {
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (state.supports_effects()) {
root[ESPHOME_F("effect")] = state.get_effect_name();
root[ESPHOME_F("effect")] = state.get_effect_name_ref();
root[ESPHOME_F("effect_index")] = state.get_current_effect_index();
root[ESPHOME_F("effect_count")] = state.get_effect_count();
}
+15 -1
View File
@@ -1,10 +1,14 @@
#ifdef USE_ESP32_VARIANT_ESP32P4
#include <utility>
#include "mipi_dsi.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace mipi_dsi {
// Maximum bytes to log for init commands (truncated if larger)
static constexpr size_t MIPI_DSI_MAX_CMD_LOG_BYTES = 64;
static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) {
auto *sem = static_cast<SemaphoreHandle_t *>(user_ctx);
BaseType_t need_yield = pdFALSE;
@@ -121,8 +125,11 @@ void MIPI_DSI::setup() {
}
}
const auto *ptr = vec.data() + index;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "Command %02X, length %d, byte(s) %s", cmd, num_args,
format_hex_pretty(ptr, num_args, '.', false).c_str());
format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
err = esp_lcd_panel_io_tx_param(this->io_handle_, cmd, ptr, num_args);
if (err != ESP_OK) {
this->smark_failed(LOG_STR("lcd_panel_io_tx_param failed"), err);
@@ -293,6 +300,13 @@ void MIPI_DSI::draw_pixel_at(int x, int y, Color color) {
void MIPI_DSI::fill(Color color) {
if (!this->check_buffer_())
return;
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
switch (this->color_depth_) {
case display::COLOR_BITNESS_565: {
auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
+13 -1
View File
@@ -1,5 +1,6 @@
#ifdef USE_ESP32_VARIANT_ESP32S3
#include "mipi_rgb.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esp_lcd_panel_rgb.h"
@@ -8,6 +9,9 @@ namespace esphome {
namespace mipi_rgb {
static const uint8_t DELAY_FLAG = 0xFF;
// Maximum bytes to log for init commands (truncated if larger)
static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64;
static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
@@ -91,8 +95,9 @@ void MipiRgbSpi::write_init_sequence_() {
delay(120); // NOLINT
}
const auto *ptr = vec.data() + index;
char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)];
ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
format_hex_pretty(ptr, num_args, '.', false).c_str());
format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
index += num_args;
this->write_command_(cmd);
while (num_args-- != 0)
@@ -300,6 +305,13 @@ void MipiRgb::draw_pixel_at(int x, int y, Color color) {
void MipiRgb::fill(Color color) {
if (!this->check_buffer_())
return;
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
+6
View File
@@ -569,6 +569,12 @@ class MipiSpiBuffer : public MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DIS
// Fills the display with a color.
void fill(Color color) override {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
display::Display::fill(color);
return;
}
this->x_low_ = 0;
this->y_low_ = this->start_line_;
this->x_high_ = WIDTH - 1;
+8 -1
View File
@@ -1,4 +1,5 @@
#include "mitsubishi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -6,6 +7,9 @@ namespace mitsubishi {
static const char *const TAG = "mitsubishi.climate";
// IR frame size for Mitsubishi climate
static constexpr size_t MITSUBISHI_FRAME_SIZE = 18;
const uint8_t MITSUBISHI_OFF = 0x00;
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
@@ -388,7 +392,10 @@ bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) {
break;
}
ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty(state_frame, 18).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MITSUBISHI_FRAME_SIZE)];
#endif
ESP_LOGV(TAG, "Receiving: %s", format_hex_pretty_to(hex_buf, state_frame, MITSUBISHI_FRAME_SIZE));
this->publish_state();
return true;
+11 -2
View File
@@ -8,6 +8,9 @@ namespace modbus {
static const char *const TAG = "modbus";
// Maximum bytes to log for Modbus frames (truncated if larger)
static constexpr size_t MODBUS_MAX_LOG_BYTES = 64;
void Modbus::setup() {
if (this->flow_control_pin_ != nullptr) {
this->flow_control_pin_->setup();
@@ -255,7 +258,10 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
this->flow_control_pin_->digital_write(false);
waiting_for_response = address;
last_send_ = millis();
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size()));
}
// Helper function for lambdas
@@ -276,7 +282,10 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false);
waiting_for_response = payload[0];
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty_to(hex_buf, payload.data(), payload.size()));
last_send_ = millis();
}
@@ -13,6 +13,9 @@ static const uint16_t SERVICE_UUID = 0xADA0;
static const uint8_t MANUFACTURER_DATA_LENGTH = 23;
static const uint16_t MANUFACTURER_ID = 0x000D;
// Maximum bytes to log in very verbose hex output
static constexpr size_t MOPEKA_MAX_LOG_BYTES = 32;
void MopekaStdCheck::dump_config() {
ESP_LOGCONFIG(TAG, "Mopeka Std Check");
ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100);
@@ -60,7 +63,11 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const auto &manu_data = manu_datas[0];
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(),
format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size()));
if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
+4 -2
View File
@@ -153,13 +153,14 @@ void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *mes
#endif
void MQTTClientComponent::dump_config() {
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
"MQTT:\n"
" Server Address: %s:%u (%s)\n"
" Username: " LOG_SECRET("'%s'") "\n"
" Client ID: " LOG_SECRET("'%s'") "\n"
" Clean Session: %s",
this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str().c_str(),
this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf),
this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
YESNO(this->credentials_.clean_session));
if (this->is_discovery_ip_enabled()) {
@@ -246,7 +247,8 @@ void MQTTClientComponent::check_dnslookup_() {
return;
}
ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str().c_str());
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str_to(ip_buf));
this->start_connect_();
}
#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1
@@ -7,12 +7,14 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.arduino";
static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -89,8 +91,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
EspClass::getFreeHeap());
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(
TAG, "Recv: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
@@ -107,8 +111,10 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
buffer = nullptr;
return range_end + 1;
} else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
ESP_LOGE(TAG, "Invalid response: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGE(
TAG, "Invalid response: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
// Deallocate buffer
allocator.deallocate(buffer, 4096);
buffer = nullptr;
@@ -274,8 +280,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->recv_ret_string_(response, 5000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(response.data()), response.size()),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
@@ -9,12 +9,14 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.esp32";
static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -110,8 +112,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
#endif
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(
TAG, "Recv: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
@@ -128,8 +132,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
buffer = nullptr;
return range_end + 1;
} else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
ESP_LOGE(TAG, "Invalid response: [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGE(
TAG, "Invalid response: [%s]",
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()));
// Deallocate buffer
allocator.deallocate(buffer, 4096);
buffer = nullptr;
@@ -287,8 +293,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->recv_ret_string_(response, 5000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
format_hex_pretty_to(hex_buf, reinterpret_cast<const uint8_t *>(response.data()), response.size()),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
+3 -2
View File
@@ -7,6 +7,7 @@
#include "opentherm.h"
#include "esphome/core/helpers.h"
#include <cinttypes>
#ifdef USE_ESP32
#include "driver/timer.h"
#include "esp_err.h"
@@ -569,8 +570,8 @@ void OpenTherm::debug_data(OpenthermData &data) {
to_string(data.f88()).c_str());
}
void OpenTherm::debug_error(OpenThermError &error) const {
ESP_LOGD(TAG, "data: %s; clock: %s; capture: %s; bit_pos: %s", format_hex(error.data).c_str(),
to_string(clock_).c_str(), format_bin(error.capture).c_str(), to_string(error.bit_pos).c_str());
ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_,
error.capture, error.bit_pos);
}
float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }
@@ -1,11 +1,15 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "packet_transport.h"
#include "esphome/components/xxtea/xxtea.h"
namespace esphome {
namespace packet_transport {
// Maximum bytes to log in hex output (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t PACKET_MAX_LOG_BYTES = 168;
/**
* Structure of a data packet; everything is little-endian
*
@@ -263,7 +267,8 @@ void PacketTransport::flush_() {
xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4,
(uint32_t *) this->encryption_key_.data());
}
ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty(encode_buffer.data(), encode_buffer.size()).c_str());
char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
ESP_LOGVV(TAG, "Sending packet %s", format_hex_pretty_to(hex_buf, encode_buffer.data(), encode_buffer.size()));
this->send_packet(encode_buffer);
}
@@ -505,8 +510,9 @@ void PacketTransport::process_(const std::vector<uint8_t> &data) {
}
if (decoder.get(byte) == DECODE_OK) {
ESP_LOGW(TAG, "Unknown key %X", byte);
char hex_buf[format_hex_pretty_size(PACKET_MAX_LOG_BYTES)];
ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(),
format_hex_pretty(data).c_str());
format_hex_pretty_to(hex_buf, data.data(), data.size()));
}
break;
}
+6
View File
@@ -117,6 +117,12 @@ void PCD8544::update() {
}
void PCD8544::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
+17 -4
View File
@@ -1,4 +1,5 @@
#include "pn532_spi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
// Based on:
@@ -11,6 +12,9 @@ namespace pn532_spi {
static const char *const TAG = "pn532_spi";
// Maximum bytes to log in verbose hex output
static constexpr size_t PN532_MAX_LOG_BYTES = 64;
void PN532Spi::setup() {
this->spi_setup();
@@ -32,7 +36,10 @@ bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
delay(2);
// First byte, communication mode: Write data
this->write_byte(0x01);
ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
this->write_array(data.data(), data.size());
this->disable();
@@ -55,7 +62,10 @@ bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
this->read_array(data.data(), len);
this->disable();
data.insert(data.begin(), 0x01);
ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Read data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
return true;
}
@@ -73,7 +83,10 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
std::vector<uint8_t> header(7);
this->read_array(header.data(), 7);
ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Header data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), header.data(), header.size()));
if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) {
// invalid packet
@@ -103,7 +116,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
this->read_array(data.data(), len + 1);
this->disable();
ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str());
ESP_LOGV(TAG, "Response data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size()));
uint8_t checksum = header[5] + header[6]; // TFI + Command response code
for (int i = 0; i < len - 1; i++) {
+9 -1
View File
@@ -1,10 +1,14 @@
#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3)
#include "qspi_dbi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace qspi_dbi {
// Maximum bytes to log in verbose hex output
static constexpr size_t QSPI_DBI_MAX_LOG_BYTES = 64;
void QspiDbi::setup() {
this->spi_setup();
if (this->enable_pin_ != nullptr) {
@@ -174,7 +178,11 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui
this->disable();
}
void QspiDbi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(QSPI_DBI_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len,
format_hex_pretty_to(hex_buf, sizeof(hex_buf), bytes, len));
this->enable();
this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len);
this->disable();
+20 -5
View File
@@ -12,6 +12,9 @@ static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq
static const char *const TAG = "rc522";
// Max UID size for RFID tags (4, 7, or 10 bytes)
static constexpr size_t RC522_MAX_UID_SIZE = 10;
static const uint8_t RESET_COUNT = 5;
void RC522::setup() {
@@ -191,8 +194,9 @@ void RC522::loop() {
if (status == STATUS_TIMEOUT) {
ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status);
} else {
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_,
format_hex_pretty(buffer_, back_length_, '-', false).c_str());
format_hex_pretty_to(hex_buf, buffer_, back_length_, '-'));
}
state_ = STATE_DONE;
@@ -237,13 +241,18 @@ void RC522::loop() {
trigger->process(rfid_uid);
if (report) {
ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty(rfid_uid, '-', false).c_str());
char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGD(TAG, "Found new tag '%s'", format_hex_pretty_to(uid_buf, rfid_uid.data(), rfid_uid.size(), '-'));
}
break;
}
case STATE_DONE: {
if (!this->current_uid_.empty()) {
ESP_LOGV(TAG, "Tag '%s' removed", format_hex_pretty(this->current_uid_, '-', false).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "Tag '%s' removed",
format_hex_pretty_to(uid_buf, this->current_uid_.data(), this->current_uid_.size(), '-'));
#endif
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(this->current_uid_);
}
@@ -338,7 +347,10 @@ void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
void RC522::pcd_transceive_data_(uint8_t send_len) {
ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty(buffer_, send_len, '-', false).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_hex_pretty_to(hex_buf, buffer_, send_len, '-'));
#endif
delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands
send_len_ = send_len;
// Prepare values for BitFramingReg
@@ -412,8 +424,11 @@ RC522::StatusCode RC522::await_transceive_() {
error_reg_value); // TODO: is this always due to collissions?
return STATUS_ERROR;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
ESP_LOGV(TAG, "received %d bytes: %s", back_length_,
format_hex_pretty(buffer_ + send_len_, back_length_, '-', false).c_str());
format_hex_pretty_to(hex_buf, buffer_ + send_len_, back_length_, '-'));
#endif
return STATUS_OK;
}
@@ -1,4 +1,5 @@
#include "haier_protocol.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -12,6 +13,8 @@ constexpr uint32_t BIT_MARK_US = 540;
constexpr uint32_t BIT_ONE_SPACE_US = 1650;
constexpr uint32_t BIT_ZERO_SPACE_US = 580;
constexpr unsigned int HAIER_IR_PACKET_BIT_SIZE = 112;
// Max data bytes in packet (excluding checksum)
constexpr size_t HAIER_MAX_DATA_BYTES = (HAIER_IR_PACKET_BIT_SIZE / 8);
void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) {
for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) {
@@ -77,7 +80,8 @@ optional<HaierData> HaierProtocol::decode(RemoteReceiveData src) {
}
void HaierProtocol::dump(const HaierData &data) {
ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(HAIER_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
} // namespace remote_base
@@ -1,4 +1,5 @@
#include "mirage_protocol.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -13,9 +14,12 @@ constexpr uint32_t BIT_ONE_SPACE_US = 1592;
constexpr uint32_t BIT_ZERO_SPACE_US = 545;
constexpr unsigned int MIRAGE_IR_PACKET_BIT_SIZE = 120;
// Max data bytes in packet (excluding checksum)
constexpr size_t MIRAGE_MAX_DATA_BYTES = (MIRAGE_IR_PACKET_BIT_SIZE / 8);
void MirageProtocol::encode(RemoteTransmitData *dst, const MirageData &data) {
ESP_LOGI(TAG, "Transive Mirage: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
dst->set_carrier_frequency(38000);
dst->reserve(5 + ((data.data.size() + 1) * 2));
dst->mark(HEADER_MARK_US);
@@ -77,7 +81,8 @@ optional<MirageData> MirageProtocol::decode(RemoteReceiveData src) {
}
void MirageProtocol::dump(const MirageData &data) {
ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty(data.data).c_str());
char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)];
ESP_LOGI(TAG, "Received Mirage: %s", format_hex_pretty_to(hex_buf, data.data.data(), data.data.size()));
}
} // namespace remote_base
@@ -10,6 +10,9 @@ namespace seeed_mr60bha2 {
static const char *const TAG = "seeed_mr60bha2";
// Maximum bytes to log in verbose hex output
static constexpr size_t MR60BHA2_MAX_LOG_BYTES = 64;
// Prints the component's configuration data. dump_config() prints all of the component's configuration
// items in an easy-to-read format, including the configuration key-value pairs.
void MR60BHA2Component::dump_config() {
@@ -110,7 +113,10 @@ bool MR60BHA2Component::validate_message_() {
if (at == 7) {
if (!validate_checksum(data, 7, header_checksum)) {
ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum);
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8));
return false;
}
return true;
@@ -125,14 +131,22 @@ bool MR60BHA2Component::validate_message_() {
if (at == 8 + length) {
if (!validate_checksum(data + 8, length, data_checksum)) {
ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum);
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data, 8 + length));
return false;
}
}
const uint8_t *frame_data = data + 8;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf1[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)];
char hex_buf2[format_hex_pretty_size(MR60BHA2_MAX_LOG_BYTES)];
#endif
ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type,
format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str());
format_hex_pretty_to(hex_buf1, sizeof(hex_buf1), frame_data, length),
format_hex_pretty_to(hex_buf2, sizeof(hex_buf2), this->rx_message_.data(), this->rx_message_.size()));
this->process_frame_(frame_id, frame_type, data + 8, length);
// Return false to reset rx buffer
@@ -10,6 +10,9 @@ namespace seeed_mr60fda2 {
static const char *const TAG = "seeed_mr60fda2";
// Maximum bytes to log in verbose hex output
static constexpr size_t MR60FDA2_MAX_LOG_BYTES = 64;
// Prints the component's configuration data. dump_config() prints all of the component's configuration
// items in an easy-to-read format, including the configuration key-value pairs.
void MR60FDA2Component::dump_config() {
@@ -202,9 +205,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) {
this->current_frame_locate_++;
} else {
ESP_LOGD(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", buffer);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)];
char byte_buf[format_hex_pretty_size(1)];
#endif
ESP_LOGV(TAG, "CURRENT_FRAME: %s %s",
format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(),
format_hex_pretty(&buffer, 1).c_str());
format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_),
format_hex_pretty_to(byte_buf, &buffer, 1));
this->current_frame_locate_ = LOCATE_FRAME_HEADER;
}
break;
@@ -228,9 +235,13 @@ void MR60FDA2Component::split_frame_(uint8_t buffer) {
this->process_frame_();
} else {
ESP_LOGD(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", buffer);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char frame_buf[format_hex_pretty_size(MR60FDA2_MAX_LOG_BYTES)];
char byte_buf[format_hex_pretty_size(1)];
#endif
ESP_LOGV(TAG, "GET CURRENT_FRAME: %s %s",
format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(),
format_hex_pretty(&buffer, 1).c_str());
format_hex_pretty_to(frame_buf, this->current_frame_buf_, this->current_frame_len_),
format_hex_pretty_to(byte_buf, &buffer, 1));
this->current_frame_locate_ = LOCATE_FRAME_HEADER;
}
@@ -328,7 +339,10 @@ void MR60FDA2Component::set_install_height(uint8_t index) {
float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]);
send_data[12] = calculate_checksum(send_data + 8, 4);
this->write_array(send_data, 13);
ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty(send_data, 13).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(13)];
#endif
ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty_to(hex_buf, send_data, 13));
}
void MR60FDA2Component::set_height_threshold(uint8_t index) {
@@ -336,7 +350,10 @@ void MR60FDA2Component::set_height_threshold(uint8_t index) {
float_to_bytes(HEIGHT_THRESHOLD[index], &send_data[8]);
send_data[12] = calculate_checksum(send_data + 8, 4);
this->write_array(send_data, 13);
ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty(send_data, 13).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(13)];
#endif
ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty_to(hex_buf, send_data, 13));
}
void MR60FDA2Component::set_sensitivity(uint8_t index) {
@@ -346,19 +363,28 @@ void MR60FDA2Component::set_sensitivity(uint8_t index) {
send_data[12] = calculate_checksum(send_data + 8, 4);
this->write_array(send_data, 13);
ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty(send_data, 13).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(13)];
#endif
ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty_to(hex_buf, send_data, 13));
}
void MR60FDA2Component::get_radar_parameters() {
uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x06, 0xF6};
this->write_array(send_data, 8);
ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty(send_data, 8).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(8)];
#endif
ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty_to(hex_buf, send_data, 8));
}
void MR60FDA2Component::factory_reset() {
uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x10, 0xCF};
this->write_array(send_data, 8);
ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty(send_data, 8).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(8)];
#endif
ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty_to(hex_buf, send_data, 8));
this->get_radar_parameters();
}
@@ -270,7 +270,10 @@ void ShellyDimmer::send_settings_() {
}
bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) {
ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str());
// Buffer for hex formatting: max frame size * 2 + null (covers any payload)
char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1];
ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len,
format_hex_to(hex_buf, sizeof(hex_buf), payload, len));
// Prepare a command frame.
uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];
+1 -1
View File
@@ -70,7 +70,7 @@ void SN74HC595GPIOComponent::write_gpio() {
void SN74HC595SPIComponent::write_gpio() {
for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) {
this->enable();
this->transfer_byte(output_byte);
this->write_byte(output_byte);
this->disable();
}
SN74HC595Component::write_gpio();
+19 -4
View File
@@ -42,12 +42,17 @@
* M 6C - CRC over bytes 2 to F (Addition)
\*********************************************************************************************/
#include "sonoff_d1.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace sonoff_d1 {
static const char *const TAG = "sonoff_d1";
// Protocol constants
static constexpr size_t SONOFF_D1_ACK_SIZE = 7;
static constexpr size_t SONOFF_D1_MAX_CMD_SIZE = 17;
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
uint8_t crc = 0;
for (size_t i = 2; i < len - 1; i++) {
@@ -86,8 +91,11 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
// Read a minimal packet
if (this->read_array(cmd, 6)) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(6)];
ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str());
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, 6));
#endif
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
@@ -101,7 +109,10 @@ bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
return false;
}
if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) {
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf2[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)];
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf2, &cmd[6], cmd[5] + 1));
#endif
// Check the checksum
uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7);
@@ -145,9 +156,10 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_);
return true;
} else {
char hex_buf[format_hex_pretty_size(SONOFF_D1_ACK_SIZE)];
ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:",
this->write_count_);
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str());
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, ref_buffer, sizeof(ref_buffer)));
}
return false;
}
@@ -174,8 +186,11 @@ bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_a
// 2. UART command initiated by this component can clash with a command initiated by RF
uint32_t retries = 10;
do {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(SONOFF_D1_MAX_CMD_SIZE)];
ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_);
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str());
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty_to(hex_buf, cmd, len));
#endif
this->write_array(cmd, len);
this->write_count_++;
if (!needs_ack)
+67 -27
View File
@@ -49,21 +49,60 @@ SPIDevice = spi_ns.class_("SPIDevice")
SPIDataRate = spi_ns.enum("SPIDataRate")
SPIMode = spi_ns.enum("SPIMode")
SPI_DATA_RATE_OPTIONS = {
80e6: SPIDataRate.DATA_RATE_80MHZ,
40e6: SPIDataRate.DATA_RATE_40MHZ,
20e6: SPIDataRate.DATA_RATE_20MHZ,
10e6: SPIDataRate.DATA_RATE_10MHZ,
8e6: SPIDataRate.DATA_RATE_8MHZ,
5e6: SPIDataRate.DATA_RATE_5MHZ,
4e6: SPIDataRate.DATA_RATE_4MHZ,
2e6: SPIDataRate.DATA_RATE_2MHZ,
1e6: SPIDataRate.DATA_RATE_1MHZ,
2e5: SPIDataRate.DATA_RATE_200KHZ,
75e3: SPIDataRate.DATA_RATE_75KHZ,
1e3: SPIDataRate.DATA_RATE_1KHZ,
PLATFORM_SPI_CLOCKS = {
PLATFORM_ESP8266: 40e6,
PLATFORM_ESP32: 80e6,
PLATFORM_RP2040: 62.5e6,
}
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
MAX_DATA_RATE_ERROR = 0.05 # Max allowable actual data rate difference from requested
def _render_hz(value: float) -> str:
"""Render a frequency in Hz as a human-readable string using Hz, KHz or MHz.
Examples:
500 -> "500 Hz"
1500 -> "1.5 kHz"
2000000 -> "2 MHz"
"""
if value >= 1e6:
unit = "MHz"
num = value / 1e6
elif value >= 1e3:
unit = "kHz"
num = value / 1e3
else:
unit = "Hz"
num = value
# Format with up to 2 decimal places, then strip unnecessary trailing zeros and dot
formatted = f"{int(num)}" if unit == "Hz" else f"{num:.2f}".rstrip("0").rstrip(".")
return formatted + unit
def _frequency_validator(value):
platform = get_target_platform()
frequency = PLATFORM_SPI_CLOCKS[platform]
value = cv.frequency(value)
if value > frequency:
raise cv.Invalid(
f"The configured SPI data rate ({_render_hz(value)}) exceeds the maximum for this platform ({_render_hz(frequency)})"
)
if value < 1000:
raise cv.Invalid("The configured SPI data rate must be at least 1000Hz")
divisor = round(frequency / value)
actual = frequency / divisor
error = abs(actual - value) / value
if error > MAX_DATA_RATE_ERROR:
raise cv.Invalid(
f"The configured SPI data rate ({_render_hz(value)}) is not available for this chip - closest is {_render_hz(actual)}"
)
return value
SPI_DATA_RATE_SCHEMA = _frequency_validator
SPI_MODE_OPTIONS = {
"MODE0": SPIMode.MODE0,
@@ -393,19 +432,20 @@ def spi_device_schema(
:param mode Choose single, quad or octal mode.
:return: The SPI device schema, `extend` this in your config schema.
"""
schema = {
cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]),
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32),
}
if cs_pin_required:
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
else:
schema[cv.Optional(CONF_CS_PIN)] = pins.gpio_output_pin_schema
return cv.Schema(schema)
cs_pin_option = cv.Required if cs_pin_required else cv.Optional
return cv.Schema(
{
cv.GenerateID(CONF_SPI_ID): cv.use_id(TYPE_CLASS[mode]),
cv.Optional(
CONF_DATA_RATE, default=default_data_rate
): SPI_DATA_RATE_SCHEMA,
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32),
cs_pin_option(CONF_CS_PIN): pins.gpio_output_pin_schema,
}
)
async def register_spi_device(var, config):
@@ -329,6 +329,12 @@ void HOT SSD1306::draw_absolute_pixel_internal(int x, int y, Color color) {
}
}
void SSD1306::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint8_t fill = color.is_on() ? 0xFF : 0x00;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
@@ -174,6 +174,12 @@ void HOT SSD1322::draw_absolute_pixel_internal(int x, int y, Color color) {
this->buffer_[pos] |= color4;
}
void SSD1322::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color);
uint8_t fill = (color4 & SSD1322_COLORMASK) | ((color4 & SSD1322_COLORMASK) << SSD1322_COLORSHIFT);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
@@ -150,6 +150,12 @@ void HOT SSD1327::draw_absolute_pixel_internal(int x, int y, Color color) {
this->buffer_[pos] |= color4;
}
void SSD1327::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
const uint32_t color4 = display::ColorUtil::color_to_grayscale4(color);
uint8_t fill = (color4 & SSD1327_COLORMASK) | ((color4 & SSD1327_COLORMASK) << SSD1327_COLORSHIFT);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
@@ -128,6 +128,12 @@ void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) {
this->buffer_[pos] = color565 & 0xff;
}
void SSD1331::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
const uint32_t color565 = display::ColorUtil::color_to_565(color);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) {
if (i & 1) {
@@ -160,6 +160,12 @@ void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) {
this->buffer_[pos] = color565 & 0xff;
}
void SSD1351::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
const uint32_t color565 = display::ColorUtil::color_to_565(color);
for (uint32_t i = 0; i < this->get_buffer_length_(); i++) {
if (i & 1) {
+10 -1
View File
@@ -131,7 +131,16 @@ void HOT ST7567::draw_absolute_pixel_internal(int x, int y, Color color) {
}
}
void ST7567::fill(Color color) { memset(buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); }
void ST7567::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint8_t fill = color.is_on() ? 0xFF : 0x00;
memset(buffer_, fill, this->get_buffer_length_());
}
void ST7567::init_reset_() {
if (this->reset_pin_ != nullptr) {
+10 -1
View File
@@ -89,7 +89,16 @@ void HOT ST7920::write_display_data() {
}
}
void ST7920::fill(Color color) { memset(this->buffer_, color.is_on() ? 0xFF : 0x00, this->get_buffer_length_()); }
void ST7920::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
uint8_t fill = color.is_on() ? 0xFF : 0x00;
memset(this->buffer_, fill, this->get_buffer_length_());
}
void ST7920::dump_config() {
LOG_DISPLAY("", "ST7920", this);
+21 -5
View File
@@ -20,6 +20,8 @@ static const char *const TAG = "tuya";
static const int COMMAND_DELAY = 10;
static const int RECEIVE_TIMEOUT = 300;
static const int MAX_RETRIES = 5;
// Max bytes to log for datapoint values (larger values are truncated)
static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16;
void Tuya::setup() {
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
@@ -51,7 +53,9 @@ void Tuya::dump_config() {
}
for (auto &info : this->datapoints_) {
if (info.type == TuyaDatapointType::RAW) {
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str());
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id,
format_hex_pretty_to(hex_buf, info.value_raw.data(), info.value_raw.size()));
} else if (info.type == TuyaDatapointType::BOOLEAN) {
ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
} else if (info.type == TuyaDatapointType::INTEGER) {
@@ -122,8 +126,11 @@ bool Tuya::validate_message_() {
// valid message
const uint8_t *message_data = data + 6;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
format_hex_pretty_to(hex_buf, message_data, length), static_cast<uint8_t>(this->init_state_));
#endif
this->handle_command_(command, version, message_data, length);
// return false to reset rx buffer
@@ -349,7 +356,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
{
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id,
format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size()));
}
break;
case TuyaDatapointType::BOOLEAN:
if (data_size != 1) {
@@ -460,8 +471,12 @@ void Tuya::send_raw_command_(TuyaCommand command) {
break;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()),
static_cast<uint8_t>(this->init_state_));
#endif
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
if (!command.payload.empty())
@@ -675,7 +690,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
}
void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size()));
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
+5 -2
View File
@@ -65,11 +65,14 @@ void UDPComponent::setup() {
server.sin_port = htons(this->listen_port_);
if (this->listen_address_.has_value()) {
// Only 16 bytes needed for IPv4, but use standard size for consistency
char addr_buf[network::IP_ADDRESS_BUFFER_SIZE];
this->listen_address_.value().str_to(addr_buf);
struct ip_mreq imreq = {};
imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY;
inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr);
inet_aton(addr_buf, &imreq.imr_multiaddr);
server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr;
ESP_LOGD(TAG, "Join multicast %s", this->listen_address_.value().str().c_str());
ESP_LOGD(TAG, "Join multicast %s", addr_buf);
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
if (err < 0) {
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
+1 -1
View File
@@ -28,7 +28,7 @@ CONFIG_SCHEMA = (
)
.extend(
{
cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_TRIGGER_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance,
cv.Optional(
@@ -1,64 +1,96 @@
#include "ultrasonic_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ultrasonic {
namespace esphome::ultrasonic {
static const char *const TAG = "ultrasonic.sensor";
static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us (noise filtering)
static constexpr uint32_t TIMEOUT_MARGIN_US = 1000; // Extra margin for sensor processing time
void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) {
uint32_t now = micros();
if (!arg->echo_start || (now - arg->echo_start_us) <= DEBOUNCE_US) {
arg->echo_start_us = now;
arg->echo_start = true;
} else {
arg->echo_end_us = now;
arg->echo_end = true;
}
}
void IRAM_ATTR UltrasonicSensorComponent::send_trigger_pulse_() {
InterruptLock lock;
this->store_.echo_start_us = 0;
this->store_.echo_end_us = 0;
this->store_.echo_start = false;
this->store_.echo_end = false;
this->trigger_pin_isr_.digital_write(true);
delayMicroseconds(this->pulse_time_us_);
this->trigger_pin_isr_.digital_write(false);
this->measurement_pending_ = true;
this->measurement_start_us_ = micros();
}
void UltrasonicSensorComponent::setup() {
this->trigger_pin_->setup();
this->trigger_pin_->digital_write(false);
this->trigger_pin_isr_ = this->trigger_pin_->to_isr();
this->echo_pin_->setup();
// isr is faster to access
echo_isr_ = echo_pin_->to_isr();
this->echo_pin_->attach_interrupt(UltrasonicSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
}
void UltrasonicSensorComponent::update() {
this->trigger_pin_->digital_write(true);
delayMicroseconds(this->pulse_time_us_);
this->trigger_pin_->digital_write(false);
if (this->measurement_pending_) {
return;
}
this->send_trigger_pulse_();
}
const uint32_t start = micros();
while (micros() - start < timeout_us_ && echo_isr_.digital_read())
;
while (micros() - start < timeout_us_ && !echo_isr_.digital_read())
;
const uint32_t pulse_start = micros();
while (micros() - start < timeout_us_ && echo_isr_.digital_read())
;
const uint32_t pulse_end = micros();
void UltrasonicSensorComponent::loop() {
if (!this->measurement_pending_) {
return;
}
ESP_LOGV(TAG, "Echo took %" PRIu32 "µs", pulse_end - pulse_start);
if (pulse_end - start >= timeout_us_) {
ESP_LOGD(TAG, "'%s' - Distance measurement timed out!", this->name_.c_str());
this->publish_state(NAN);
} else {
float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start);
if (this->store_.echo_end) {
uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us;
ESP_LOGV(TAG, "Echo took %" PRIu32 "us", pulse_duration);
float result = UltrasonicSensorComponent::us_to_m(pulse_duration);
ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result);
this->publish_state(result);
this->measurement_pending_ = false;
return;
}
uint32_t elapsed = micros() - this->measurement_start_us_;
if (elapsed >= this->timeout_us_ + TIMEOUT_MARGIN_US) {
ESP_LOGD(TAG,
"'%s' - Timeout after %" PRIu32 "us (measurement_start=%" PRIu32 ", echo_start=%" PRIu32
", echo_end=%" PRIu32 ")",
this->name_.c_str(), elapsed, this->measurement_start_us_, this->store_.echo_start_us,
this->store_.echo_end_us);
this->publish_state(NAN);
this->measurement_pending_ = false;
}
}
void UltrasonicSensorComponent::dump_config() {
LOG_SENSOR("", "Ultrasonic Sensor", this);
LOG_PIN(" Echo Pin: ", this->echo_pin_);
LOG_PIN(" Trigger Pin: ", this->trigger_pin_);
ESP_LOGCONFIG(TAG,
" Pulse time: %" PRIu32 " µs\n"
" Timeout: %" PRIu32 " µs",
" Pulse time: %" PRIu32 " us\n"
" Timeout: %" PRIu32 " us",
this->pulse_time_us_, this->timeout_us_);
LOG_UPDATE_INTERVAL(this);
}
float UltrasonicSensorComponent::us_to_m(uint32_t us) {
const float speed_sound_m_per_s = 343.0f;
const float time_s = us / 1e6f;
const float total_dist = time_s * speed_sound_m_per_s;
return total_dist / 2.0f;
}
float UltrasonicSensorComponent::get_setup_priority() const { return setup_priority::DATA; }
void UltrasonicSensorComponent::set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; }
void UltrasonicSensorComponent::set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; }
} // namespace ultrasonic
} // namespace esphome
} // namespace esphome::ultrasonic
@@ -6,41 +6,49 @@
#include <cinttypes>
namespace esphome {
namespace ultrasonic {
namespace esphome::ultrasonic {
struct UltrasonicSensorStore {
static void gpio_intr(UltrasonicSensorStore *arg);
volatile uint32_t echo_start_us{0};
volatile uint32_t echo_end_us{0};
volatile bool echo_start{false};
volatile bool echo_end{false};
};
class UltrasonicSensorComponent : public sensor::Sensor, public PollingComponent {
public:
void set_trigger_pin(GPIOPin *trigger_pin) { trigger_pin_ = trigger_pin; }
void set_echo_pin(InternalGPIOPin *echo_pin) { echo_pin_ = echo_pin; }
void set_trigger_pin(InternalGPIOPin *trigger_pin) { this->trigger_pin_ = trigger_pin; }
void set_echo_pin(InternalGPIOPin *echo_pin) { this->echo_pin_ = echo_pin; }
/// Set the timeout for waiting for the echo in µs.
void set_timeout_us(uint32_t timeout_us);
void set_timeout_us(uint32_t timeout_us) { this->timeout_us_ = timeout_us; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Set up pins and register interval.
void setup() override;
void loop() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
float get_setup_priority() const override { return setup_priority::DATA; }
/// Set the time in µs the trigger pin should be enabled for in µs, defaults to 10µs (for HC-SR04)
void set_pulse_time_us(uint32_t pulse_time_us);
void set_pulse_time_us(uint32_t pulse_time_us) { this->pulse_time_us_ = pulse_time_us; }
protected:
/// Helper function to convert the specified echo duration in µs to meters.
static float us_to_m(uint32_t us);
/// Helper function to convert the specified distance in meters to the echo duration in µs.
void send_trigger_pulse_();
GPIOPin *trigger_pin_;
InternalGPIOPin *trigger_pin_;
ISRInternalGPIOPin trigger_pin_isr_;
InternalGPIOPin *echo_pin_;
ISRInternalGPIOPin echo_isr_;
uint32_t timeout_us_{}; /// 2 meters.
UltrasonicSensorStore store_;
uint32_t timeout_us_{};
uint32_t pulse_time_us_{};
uint32_t measurement_start_us_{0};
bool measurement_pending_{false};
};
} // namespace ultrasonic
} // namespace esphome
} // namespace esphome::ultrasonic
+12 -2
View File
@@ -1,6 +1,7 @@
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "usb_cdc_acm.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <sys/param.h>
@@ -16,6 +17,9 @@ namespace esphome::usb_cdc_acm {
static const char *TAG = "usb_cdc_acm";
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
@@ -43,7 +47,10 @@ static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
esp_err_t ret =
tinyusb_cdcacm_read(static_cast<tinyusb_cdcacm_itf_t>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty(rx_buf, rx_size).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size));
if (ret == ESP_OK && rx_size > 0) {
RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
@@ -306,7 +313,10 @@ void USBCDCACMInstance::usb_tx_task() {
}
ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size);
ESP_LOGVV(TAG, "data = %s", format_hex_pretty(data, tx_data_size).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size));
// Serial data will be split up into 64 byte chunks to be sent over USB so this
// usually will take multiple iterations
@@ -272,7 +272,8 @@ void VoiceAssistant::loop() {
size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
if (this->audio_mode_ == AUDIO_MODE_API) {
api::VoiceAssistantAudio msg;
msg.set_data(this->send_buffer_, read_bytes);
msg.data = this->send_buffer_;
msg.data_len = read_bytes;
this->api_client_->send_message(msg, api::VoiceAssistantAudio::MESSAGE_TYPE);
} else {
if (!this->udp_socket_running_) {
@@ -627,9 +628,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 +649,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 +694,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 +706,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 +732,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 +779,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") {
@@ -841,12 +842,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
#ifdef USE_SPEAKER // We should never get to this function if there is no speaker anyway
if ((this->speaker_ != nullptr) && (this->speaker_buffer_ != nullptr)) {
if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) {
memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length());
this->speaker_buffer_index_ += msg.data.length();
this->speaker_buffer_size_ += msg.data.length();
this->speaker_bytes_received_ += msg.data.length();
ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data.length());
if (this->speaker_buffer_index_ + msg.data_len < SPEAKER_BUFFER_SIZE) {
memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data, msg.data_len);
this->speaker_buffer_index_ += msg.data_len;
this->speaker_buffer_size_ += msg.data_len;
this->speaker_bytes_received_ += msg.data_len;
ESP_LOGV(TAG, "Received audio: %u bytes from API", msg.data_len);
} else {
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
}
@@ -172,6 +172,12 @@ void WaveshareEPaperBase::update() {
this->display();
}
void WaveshareEPaper::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
return;
}
// flip logic
const uint8_t fill = color.is_on() ? 0x00 : 0xFF;
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
@@ -234,6 +240,12 @@ uint8_t WaveshareEPaper7C::color_to_hex(Color color) {
return hex_code;
}
void WaveshareEPaper7C::fill(Color color) {
// If clipping is active, use base class (3-bit packing is complex for partial fills)
if (this->get_clipping().is_set()) {
display::Display::fill(color);
return;
}
uint8_t pixel_color;
if (color.is_on()) {
pixel_color = this->color_to_hex(color);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+228 -87
View File
@@ -45,62 +45,144 @@ static constexpr size_t PSTR_LOCAL_SIZE = 18;
#define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1)
// Parse URL and return match info
static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) {
UrlMatch match{};
// URL formats (disambiguated by HTTP method for 3-segment case):
// GET /{domain}/{entity_name} - main device state
// POST /{domain}/{entity_name}/{action} - main device action
// GET /{domain}/{device_name}/{entity_name} - sub-device state (USE_DEVICES only)
// POST /{domain}/{device_name}/{entity_name}/{action} - sub-device action (USE_DEVICES only)
static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain, bool is_post = false) {
// URL must start with '/' and have content after it
if (url_len < 2 || url_ptr[0] != '/')
return UrlMatch{};
// URL must start with '/'
if (url_len < 2 || url_ptr[0] != '/') {
return match;
}
// Skip leading '/'
const char *start = url_ptr + 1;
const char *p = url_ptr + 1;
const char *end = url_ptr + url_len;
// Find domain (everything up to next '/' or end)
const char *domain_end = (const char *) memchr(start, '/', end - start);
if (!domain_end) {
// No second slash found - original behavior returns invalid
return match;
}
// Helper to find next segment: returns pointer after '/' or nullptr if no more slashes
auto next_segment = [&end](const char *start) -> const char * {
const char *slash = (const char *) memchr(start, '/', end - start);
return slash ? slash + 1 : nullptr;
};
// Set domain
match.domain = start;
match.domain_len = domain_end - start;
// Helper to make StringRef from segment start to next segment (or end)
auto make_ref = [&end](const char *start, const char *next_start) -> StringRef {
return StringRef(start, (next_start ? next_start - 1 : end) - start);
};
// Parse domain segment
const char *s1 = p;
const char *s2 = next_segment(s1);
// Must have domain with trailing slash
if (!s2)
return UrlMatch{};
UrlMatch match{};
match.domain = make_ref(s1, s2);
match.valid = true;
if (only_domain) {
if (only_domain || s2 >= end)
return match;
}
// Parse ID if present
if (domain_end + 1 >= end) {
return match; // Nothing after domain slash
}
// Parse remaining segments only when needed
const char *s3 = next_segment(s2);
const char *s4 = s3 ? next_segment(s3) : nullptr;
const char *id_start = domain_end + 1;
const char *id_end = (const char *) memchr(id_start, '/', end - id_start);
StringRef seg2 = make_ref(s2, s3);
StringRef seg3 = s3 ? make_ref(s3, s4) : StringRef();
StringRef seg4 = s4 ? make_ref(s4, nullptr) : StringRef();
if (!id_end) {
// No more slashes, entire remaining string is ID
match.id = id_start;
match.id_len = end - id_start;
return match;
}
// Reject empty segments
if (seg2.empty() || (s3 && seg3.empty()) || (s4 && seg4.empty()))
return UrlMatch{};
// Set ID
match.id = id_start;
match.id_len = id_end - id_start;
// Parse method if present
if (id_end + 1 < end) {
match.method = id_end + 1;
match.method_len = end - (id_end + 1);
// Interpret based on segment count
if (!s3) {
// 1 segment after domain: /{domain}/{entity}
match.id = seg2;
} else if (!s4) {
// 2 segments after domain: /{domain}/{X}/{Y}
// HTTP method disambiguates: GET = device/entity, POST = entity/action
if (is_post) {
match.id = seg2;
match.method = seg3;
return match;
}
#ifdef USE_DEVICES
match.device_name = seg2;
match.id = seg3;
#else
return UrlMatch{}; // 3-segment GET not supported without USE_DEVICES
#endif
} else {
// 3 segments after domain: /{domain}/{device}/{entity}/{action}
#ifdef USE_DEVICES
if (!is_post) {
return UrlMatch{}; // 4-segment GET not supported (action requires POST)
}
match.device_name = seg2;
match.id = seg3;
match.method = seg4;
#else
return UrlMatch{}; // Not supported without USE_DEVICES
#endif
}
return match;
}
EntityMatchResult UrlMatch::match_entity(EntityBase *entity) const {
EntityMatchResult result{false, this->method.empty()};
#ifdef USE_DEVICES
Device *entity_device = entity->get_device();
bool url_has_device = !this->device_name.empty();
bool entity_has_device = (entity_device != nullptr);
// Device matching: URL device segment must match entity's device
if (url_has_device != entity_has_device) {
return result; // Mismatch: one has device, other doesn't
}
if (url_has_device && this->device_name != entity_device->get_name()) {
return result; // Device name doesn't match
}
#endif
// Try matching by entity name (new format)
if (this->id == entity->get_name()) {
result.matched = true;
return result;
}
// Fall back to object_id (deprecated format)
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = entity->get_object_id_to(object_id_buf);
if (this->id == object_id) {
result.matched = true;
// Log deprecation warning
#ifdef USE_DEVICES
Device *device = entity->get_device();
if (device != nullptr) {
ESP_LOGW(TAG,
"Deprecated URL format: /%.*s/%.*s/%.*s - use entity name '/%.*s/%s/%s' instead. "
"Object ID URLs will be removed in 2026.7.0.",
(int) this->domain.size(), this->domain.c_str(), (int) this->device_name.size(),
this->device_name.c_str(), (int) this->id.size(), this->id.c_str(), (int) this->domain.size(),
this->domain.c_str(), device->get_name(), entity->get_name().c_str());
} else
#endif
{
ESP_LOGW(TAG,
"Deprecated URL format: /%.*s/%.*s - use entity name '/%.*s/%s' instead. "
"Object ID URLs will be removed in 2026.7.0.",
(int) this->domain.size(), this->domain.c_str(), (int) this->id.size(), this->id.c_str(),
(int) this->domain.size(), this->domain.c_str(), entity->get_name().c_str());
}
}
return result;
}
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
// helper for allowing only unique entries in the queue
void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) {
@@ -397,15 +479,53 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
#endif
// Helper functions to reduce code size by avoiding macro expansion
// Build unique id as: {domain}/{device_name}/{entity_name} or {domain}/{entity_name}
// Uses names (not object_id) to avoid UTF-8 collision issues
static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) {
char id_buf[160]; // prefix + dash + object_id (up to 128) + null
size_t len = strlen(prefix);
memcpy(id_buf, prefix, len); // NOLINT(bugprone-not-null-terminated-result) - null added by write_object_id_to
id_buf[len++] = '-';
obj->write_object_id_to(id_buf + len, sizeof(id_buf) - len);
const StringRef &name = obj->get_name();
size_t prefix_len = strlen(prefix);
size_t name_len = name.size();
#ifdef USE_DEVICES
Device *device = obj->get_device();
const char *device_name = device ? device->get_name() : nullptr;
size_t device_len = device_name ? strlen(device_name) : 0;
#endif
// Build id into stack buffer - ArduinoJson copies the string
// Format: {prefix}/{device?}/{name}
// Buffer size guaranteed by schema validation (NAME_MAX_LENGTH=120):
// With devices: domain(20) + "/" + device(120) + "/" + name(120) + null = 263, rounded up to 280 for safety margin
// Without devices: domain(20) + "/" + name(120) + null = 142, rounded up to 150 for safety margin
#ifdef USE_DEVICES
char id_buf[280];
#else
char id_buf[150];
#endif
char *p = id_buf;
memcpy(p, prefix, prefix_len);
p += prefix_len;
*p++ = '/';
#ifdef USE_DEVICES
if (device_name) {
memcpy(p, device_name, device_len);
p += device_len;
*p++ = '/';
}
#endif
memcpy(p, name.c_str(), name_len);
p[name_len] = '\0';
root[ESPHOME_F("id")] = id_buf;
if (start_config == DETAIL_ALL) {
root[ESPHOME_F("name")] = obj->get_name();
root[ESPHOME_F("domain")] = prefix;
root[ESPHOME_F("name")] = name;
#ifdef USE_DEVICES
if (device_name) {
root[ESPHOME_F("device")] = device_name;
}
#endif
root[ESPHOME_F("icon")] = obj->get_icon_ref();
root[ESPHOME_F("entity_category")] = obj->get_entity_category();
bool is_disabled = obj->is_disabled_by_default();
@@ -444,10 +564,11 @@ void WebServer::on_sensor_update(sensor::Sensor *obj) {
}
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (sensor::Sensor *obj : App.get_sensors()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
// Note: request->method() is always HTTP_GET here (canHandle ensures this)
if (match.method_empty()) {
if (entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->sensor_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -490,10 +611,11 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj) {
}
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
// Note: request->method() is always HTTP_GET here (canHandle ensures this)
if (match.method_empty()) {
if (entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->text_sensor_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -532,10 +654,11 @@ void WebServer::on_switch_update(switch_::Switch *obj) {
}
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (switch_::Switch *obj : App.get_switches()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->switch_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -601,9 +724,10 @@ std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail
#ifdef USE_BUTTON
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (button::Button *obj : App.get_buttons()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->button_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -645,10 +769,11 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
}
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
// Note: request->method() is always HTTP_GET here (canHandle ensures this)
if (match.method_empty()) {
if (entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->binary_sensor_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -686,10 +811,11 @@ void WebServer::on_fan_update(fan::Fan *obj) {
}
void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (fan::Fan *obj : App.get_fans()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->fan_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -766,10 +892,11 @@ void WebServer::on_light_update(light::LightState *obj) {
}
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (light::LightState *obj : App.get_lights()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->light_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -844,10 +971,11 @@ void WebServer::on_cover_update(cover::Cover *obj) {
}
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (cover::Cover *obj : App.get_covers()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->cover_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -932,10 +1060,11 @@ void WebServer::on_number_update(number::Number *obj) {
}
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_numbers()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->number_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -999,9 +1128,10 @@ void WebServer::on_date_update(datetime::DateEntity *obj) {
}
void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_dates()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->date_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1062,9 +1192,10 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) {
}
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_times()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->time_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1124,9 +1255,10 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
}
void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_datetimes()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->datetime_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1188,10 +1320,11 @@ void WebServer::on_text_update(text::Text *obj) {
}
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_texts()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->text_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -1244,10 +1377,11 @@ void WebServer::on_select_update(select::Select *obj) {
}
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_selects()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : "", detail);
request->send(200, "application/json", data.c_str());
@@ -1301,10 +1435,11 @@ void WebServer::on_climate_update(climate::Climate *obj) {
}
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_climates()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->climate_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1451,10 +1586,11 @@ void WebServer::on_lock_update(lock::Lock *obj) {
}
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (lock::Lock *obj : App.get_locks()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->lock_json_(obj, obj->state, detail);
request->send(200, "application/json", data.c_str());
@@ -1525,10 +1661,11 @@ void WebServer::on_valve_update(valve::Valve *obj) {
}
void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (valve::Valve *obj : App.get_valves()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->valve_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1609,10 +1746,11 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
}
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail);
request->send(200, "application/json", data.c_str());
@@ -1690,11 +1828,12 @@ void WebServer::on_event(event::Event *obj) {
void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (event::Event *obj : App.get_events()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
// Note: request->method() is always HTTP_GET here (canHandle ensures this)
if (match.method_empty()) {
if (entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->event_json_(obj, "", detail);
request->send(200, "application/json", data.c_str());
@@ -1759,10 +1898,11 @@ void WebServer::on_update(update::UpdateEntity *obj) {
}
void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (update::UpdateEntity *obj : App.get_updates()) {
if (!match.id_equals_entity(obj))
auto entity_match = match.match_entity(obj);
if (!entity_match.matched)
continue;
if (request->method() == HTTP_GET && match.method_empty()) {
if (request->method() == HTTP_GET && entity_match.action_is_empty) {
auto detail = get_request_detail(request);
std::string data = this->update_json_(obj, detail);
request->send(200, "application/json", data.c_str());
@@ -1973,7 +2113,8 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
#endif
// Parse URL for component routing
UrlMatch match = match_url(url.c_str(), url.length(), false);
// Pass HTTP method to disambiguate 3-segment URLs (GET=sub-device state, POST=main device action)
UrlMatch match = match_url(url.c_str(), url.length(), false, request->method() == HTTP_POST);
// Route to appropriate handler based on domain
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
+18 -22
View File
@@ -35,33 +35,29 @@ extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE;
namespace esphome::web_server {
/// Result of matching a URL against an entity
struct EntityMatchResult {
bool matched; ///< True if entity matched the URL
bool action_is_empty; ///< True if no action/method segment in URL
};
/// Internal helper struct that is used to parse incoming URLs
struct UrlMatch {
const char *domain; ///< Pointer to domain within URL, for example "sensor"
const char *id; ///< Pointer to id within URL, for example "living_room_fan"
const char *method; ///< Pointer to method within URL, for example "turn_on"
uint8_t domain_len; ///< Length of domain string
uint8_t id_len; ///< Length of id string
uint8_t method_len; ///< Length of method string
bool valid; ///< Whether this match is valid
StringRef domain; ///< Domain within URL, for example "sensor"
StringRef id; ///< Entity name/id within URL, for example "Temperature"
StringRef method; ///< Method within URL, for example "turn_on"
#ifdef USE_DEVICES
StringRef device_name; ///< Device name within URL, empty for main device
#endif
bool valid{false}; ///< Whether this match is valid
// Helper methods for string comparisons
bool domain_equals(const char *str) const {
return domain && domain_len == strlen(str) && memcmp(domain, str, domain_len) == 0;
}
bool domain_equals(const char *str) const { return this->domain == str; }
bool method_equals(const char *str) const { return this->method == str; }
bool id_equals_entity(EntityBase *entity) const {
// Get object_id with zero heap allocation
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = entity->get_object_id_to(object_id_buf);
return id && id_len == object_id.size() && memcmp(id, object_id.c_str(), id_len) == 0;
}
bool method_equals(const char *str) const {
return method && method_len == strlen(str) && memcmp(method, str, method_len) == 0;
}
bool method_empty() const { return method_len == 0; }
/// Match entity by name first, then fall back to object_id with deprecation warning
/// Returns EntityMatchResult with match status and whether action segment is empty
EntityMatchResult match_entity(EntityBase *entity) const;
};
#ifdef USE_WEBSERVER_SORTING
@@ -5,6 +5,29 @@
namespace esphome::web_server {
// Write HTML-escaped text to stream (escapes ", &, <, >)
static void write_html_escaped(AsyncResponseStream *stream, const char *text) {
for (const char *p = text; *p; ++p) {
switch (*p) {
case '"':
stream->print("&quot;");
break;
case '&':
stream->print("&amp;");
break;
case '<':
stream->print("&lt;");
break;
case '>':
stream->print("&gt;");
break;
default:
stream->write(*p);
break;
}
}
}
void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
const std::function<void(AsyncResponseStream &stream, EntityBase *obj)> &action_func = nullptr) {
stream->print("<tr class=\"");
@@ -16,8 +39,27 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &
stream->print("-");
char object_id_buf[OBJECT_ID_MAX_LEN];
stream->print(obj->get_object_id_to(object_id_buf).c_str());
// Add data attributes for hierarchical URL support
stream->print("\" data-domain=\"");
stream->print(klass.c_str());
stream->print("\" data-name=\"");
write_html_escaped(stream, obj->get_name().c_str());
#ifdef USE_DEVICES
Device *device = obj->get_device();
if (device != nullptr) {
stream->print("\" data-device=\"");
write_html_escaped(stream, device->get_name());
}
#endif
stream->print("\"><td>");
stream->print(obj->get_name().c_str());
#ifdef USE_DEVICES
if (device != nullptr) {
stream->print("[");
write_html_escaped(stream, device->get_name());
stream->print("] ");
}
#endif
write_html_escaped(stream, obj->get_name().c_str());
stream->print("</td><td></td><td>");
stream->print(action.c_str());
if (action_func) {
+4 -2
View File
@@ -13,7 +13,8 @@ namespace web_server_idf {
static const char *const TAG = "web_server_idf_utils";
void url_decode(char *str) {
size_t url_decode(char *str) {
char *start = str;
char *ptr = str, buf;
for (; *str; str++, ptr++) {
if (*str == '%') {
@@ -31,7 +32,8 @@ void url_decode(char *str) {
*ptr = *str;
}
}
*ptr = *str;
*ptr = '\0';
return ptr - start;
}
bool request_has_header(httpd_req_t *req, const char *name) { return httpd_req_get_hdr_value_len(req, name); }
@@ -8,6 +8,10 @@
namespace esphome {
namespace web_server_idf {
/// Decode URL-encoded string in-place (e.g., %20 -> space, + -> space)
/// Returns the new length of the decoded string
size_t url_decode(char *str);
bool request_has_header(httpd_req_t *req, const char *name);
optional<std::string> request_get_header(httpd_req_t *req, const char *name);
optional<std::string> request_get_url_query(httpd_req_t *req);
@@ -247,11 +247,20 @@ optional<std::string> AsyncWebServerRequest::get_header(const char *name) const
}
std::string AsyncWebServerRequest::url() const {
auto *str = strchr(this->req_->uri, '?');
if (str == nullptr) {
return this->req_->uri;
auto *query_start = strchr(this->req_->uri, '?');
std::string result;
if (query_start == nullptr) {
result = this->req_->uri;
} else {
result = std::string(this->req_->uri, query_start - this->req_->uri);
}
return std::string(this->req_->uri, str - this->req_->uri);
// Decode URL-encoded characters in-place (e.g., %20 -> space)
// This matches AsyncWebServer behavior on Arduino
if (!result.empty()) {
size_t new_len = url_decode(&result[0]);
result.resize(new_len);
}
return result;
}
std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }
+4 -2
View File
@@ -45,7 +45,8 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
if (this->connecting_)
return;
// If already connected to the same AP, do nothing
if (global_wifi_component->wifi_ssid() == ssid) {
char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
return;
@@ -94,7 +95,8 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
this->cancel_timeout("wifi-connect-timeout");
this->cancel_timeout("wifi-fallback-timeout");
this->connecting_ = false;
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
} else {
+15 -5
View File
@@ -707,12 +707,15 @@ void WiFiComponent::setup_ap_config_() {
#ifdef USE_WIFI_MANUAL_IP
auto manual_ip = this->ap_.get_manual_ip();
if (manual_ip.has_value()) {
char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGCONFIG(TAG,
" AP Static IP: '%s'\n"
" AP Gateway: '%s'\n"
" AP Subnet: '%s'",
manual_ip->static_ip.str().c_str(), manual_ip->gateway.str().c_str(),
manual_ip->subnet.str().c_str());
manual_ip->static_ip.str_to(static_ip_buf), manual_ip->gateway.str_to(gateway_buf),
manual_ip->subnet.str_to(subnet_buf));
}
#endif
@@ -874,8 +877,14 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
#ifdef USE_WIFI_MANUAL_IP
if (ap.get_manual_ip().has_value()) {
ManualIP m = *ap.get_manual_ip();
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str().c_str(),
m.gateway.str().c_str(), m.subnet.str().c_str(), m.dns1.str().c_str(), m.dns2.str().c_str());
char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str_to(static_ip_buf),
m.gateway.str_to(gateway_buf), m.subnet.str_to(subnet_buf), m.dns1.str_to(dns1_buf),
m.dns2.str_to(dns2_buf));
} else
#endif
{
@@ -1225,7 +1234,8 @@ void WiFiComponent::check_connecting_finished(uint32_t now) {
auto status = this->wifi_sta_connect_status_();
if (status == WiFiSTAConnectStatus::CONNECTED) {
if (wifi_ssid().empty()) {
char ssid_buf[SSID_BUFFER_SIZE];
if (wifi_ssid_to(ssid_buf)[0] == '\0') {
ESP_LOGW(TAG, "Connection incomplete");
this->retry_connect();
return;
-10
View File
@@ -12,12 +12,6 @@
#include <string>
#include <vector>
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include <WiFi.h>
#include <WiFiType.h>
#include <esp_wifi.h>
#endif
#ifdef USE_LIBRETINY
#include <WiFi.h>
#endif
@@ -594,10 +588,6 @@ class WiFiComponent : public Component {
static void s_wifi_scan_done_callback(void *arg, STATUS status);
#endif
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info);
void wifi_scan_done_callback_();
#endif
#ifdef USE_ESP32
void wifi_process_event_(IDFWiFiEvent *data);
#endif
@@ -518,15 +518,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
switch (event->event) {
case EVENT_STAMODE_CONNECTED: {
auto it = event->event_info.connected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
it.channel);
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=%s channel=%u", it.ssid_len, (const char *) it.ssid,
format_mac_address_pretty(it.bssid).c_str(), it.channel);
s_sta_connected = true;
#ifdef USE_WIFI_LISTENERS
for (auto *listener : global_wifi_component->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -543,17 +540,15 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_STAMODE_DISCONNECTED: {
auto it = event->event_info.disconnected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
if (it.reason == REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_connect_not_found = true;
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -810,10 +805,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
network::IPAddress start_address = network::IPAddress(&info.ip);
start_address += 99;
lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
#endif
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf));
start_address += 10;
lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf));
if (!wifi_softap_set_dhcps_lease(&lease)) {
ESP_LOGE(TAG, "Set SoftAP DHCP lease failed");
return false;
@@ -734,16 +734,13 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_CONNECTED) {
const auto &it = data->data.sta_connected;
char buf[33];
assert(it.ssid_len <= 32);
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len,
(const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel,
get_auth_mode_str(it.authmode));
s_sta_connected = true;
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -757,21 +754,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
const auto &it = data->data.sta_disconnected;
char buf[33];
assert(it.ssid_len <= 32);
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
s_sta_connect_not_found = true;
} else if (it.reason == WIFI_REASON_ROAMING) {
ESP_LOGI(TAG, "Disconnected ssid='%s' reason='Station Roaming'", buf);
ESP_LOGI(TAG, "Disconnected ssid='%.*s' reason='Station Roaming'", it.ssid_len, (const char *) it.ssid);
return;
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
get_disconnect_reason_str(it.reason));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -969,10 +963,13 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
network::IPAddress start_address = network::IPAddress(&info.ip);
start_address += 99;
lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
#endif
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str_to(ip_buf));
start_address += 10;
lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str_to(ip_buf));
err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
if (err != ESP_OK) {
@@ -984,8 +981,10 @@ bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
// Configure DHCP Option 114 (Captive Portal URI) if captive portal is enabled
// This provides a standards-compliant way for clients to discover the captive portal
if (captive_portal::global_captive_portal != nullptr) {
static char captive_portal_uri[32];
snprintf(captive_portal_uri, sizeof(captive_portal_uri), "http://%s", network::IPAddress(&info.ip).str().c_str());
// Buffer must be static - dhcps_set_option_info stores pointer, doesn't copy
static char captive_portal_uri[24]; // "http://" (7) + IPv4 max (15) + null
memcpy(captive_portal_uri, "http://", 7); // NOLINT(bugprone-not-null-terminated-result) - str_to null-terminates
network::IPAddress(&info.ip).str_to(captive_portal_uri + 7);
err = esp_netif_dhcps_option(s_ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captive_portal_uri,
strlen(captive_portal_uri));
if (err != ESP_OK) {
@@ -296,14 +296,12 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
auto it = info.wifi_sta_connected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
ESP_LOGV(TAG, "Connected ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", it.ssid_len,
(const char *) it.ssid, format_mac_address_pretty(it.bssid).c_str(), it.channel,
get_auth_mode_str(it.authmode));
#ifdef USE_WIFI_LISTENERS
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid);
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
@@ -318,9 +316,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
auto it = info.wifi_sta_disconnected;
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
// LibreTiny can send spurious disconnect events with empty ssid/bssid during connection.
// These are typically "Association Leave" events that don't indicate actual failures:
@@ -339,12 +334,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
if (it.reason == WIFI_REASON_NO_AP_FOUND) {
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
ESP_LOGW(TAG, "Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
(const char *) it.ssid);
} else {
char bssid_s[18];
format_mac_addr_upper(it.bssid, bssid_s);
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, bssid_s,
get_disconnect_reason_str(it.reason));
ESP_LOGW(TAG, "Disconnected ssid='%.*s' bssid=" LOG_SECRET("%s") " reason='%s'", it.ssid_len,
(const char *) it.ssid, bssid_s, get_disconnect_reason_str(it.reason));
}
uint8_t reason = it.reason;
@@ -1,4 +1,5 @@
#include "xiaomi_cgd1.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_cgd1 {
static const char *const TAG = "xiaomi_cgd1";
static constexpr size_t CGD1_BINDKEY_SIZE = 16;
void XiaomiCGD1::dump_config() {
char bindkey_hex[format_hex_pretty_size(CGD1_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi CGD1\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, CGD1_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_cgdk2.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_cgdk2 {
static const char *const TAG = "xiaomi_cgdk2";
static constexpr size_t CGDK2_BINDKEY_SIZE = 16;
void XiaomiCGDK2::dump_config() {
char bindkey_hex[format_hex_pretty_size(CGDK2_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi CGDK2\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, CGDK2_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_cgg1.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_cgg1 {
static const char *const TAG = "xiaomi_cgg1";
static constexpr size_t CGG1_BINDKEY_SIZE = 16;
void XiaomiCGG1::dump_config() {
char bindkey_hex[format_hex_pretty_size(CGG1_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi CGG1\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, CGG1_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_lywsd02mmc.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_lywsd02mmc {
static const char *const TAG = "xiaomi_lywsd02mmc";
static constexpr size_t LYWSD02MMC_BINDKEY_SIZE = 16;
void XiaomiLYWSD02MMC::dump_config() {
char bindkey_hex[format_hex_pretty_size(LYWSD02MMC_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi LYWSD02MMC\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD02MMC_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_lywsd03mmc.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_lywsd03mmc {
static const char *const TAG = "xiaomi_lywsd03mmc";
static constexpr size_t LYWSD03MMC_BINDKEY_SIZE = 16;
void XiaomiLYWSD03MMC::dump_config() {
char bindkey_hex[format_hex_pretty_size(LYWSD03MMC_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi LYWSD03MMC\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, LYWSD03MMC_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_mhoc401.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,11 +9,14 @@ namespace xiaomi_mhoc401 {
static const char *const TAG = "xiaomi_mhoc401";
static constexpr size_t MHOC401_BINDKEY_SIZE = 16;
void XiaomiMHOC401::dump_config() {
char bindkey_hex[format_hex_pretty_size(MHOC401_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG,
"Xiaomi MHOC401\n"
" Bindkey: %s",
format_hex_pretty(this->bindkey_, 16).c_str());
format_hex_pretty_to(bindkey_hex, this->bindkey_, MHOC401_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
@@ -1,4 +1,5 @@
#include "xiaomi_rtcgq02lm.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,9 +9,12 @@ namespace xiaomi_rtcgq02lm {
static const char *const TAG = "xiaomi_rtcgq02lm";
static constexpr size_t RTCGQ02LM_BINDKEY_SIZE = 16;
void XiaomiRTCGQ02LM::dump_config() {
char bindkey_hex[format_hex_pretty_size(RTCGQ02LM_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM");
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, RTCGQ02LM_BINDKEY_SIZE, '.'));
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Motion", this->motion_);
LOG_BINARY_SENSOR(" ", "Light", this->light_);
@@ -1,4 +1,5 @@
#include "xiaomi_xmwsdj04mmc.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@@ -8,9 +9,12 @@ namespace xiaomi_xmwsdj04mmc {
static const char *const TAG = "xiaomi_xmwsdj04mmc";
static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16;
void XiaomiXMWSDJ04MMC::dump_config() {
char bindkey_hex[format_hex_pretty_size(XMWSDJ04MMC_BINDKEY_SIZE)];
ESP_LOGCONFIG(TAG, "Xiaomi XMWSDJ04MMC");
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty_to(bindkey_hex, this->bindkey_, XMWSDJ04MMC_BINDKEY_SIZE, '.'));
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
+11 -2
View File
@@ -12,6 +12,9 @@ namespace esphome::zwave_proxy {
static const char *const TAG = "zwave_proxy";
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
static constexpr size_t ZWAVE_MAX_LOG_BYTES = 168;
static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
// GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value
@@ -179,7 +182,10 @@ void ZWaveProxy::send_frame(const uint8_t *data, size_t length) {
ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]);
return;
}
ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty(data, length).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "Sending: %s", format_hex_pretty_to(hex_buf, data, length));
this->write_array(data, length);
}
@@ -252,7 +258,10 @@ bool ZWaveProxy::parse_byte_(uint8_t byte) {
this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_NAK;
} else {
this->parsing_state_ = ZWAVE_PARSING_STATE_SEND_ACK;
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(this->buffer_.data(), this->buffer_index_).c_str());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(ZWAVE_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_index_));
frame_completed = true;
}
this->response_handler_();
+39
View File
@@ -1981,6 +1981,26 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend(
)
def _validate_no_slash(value):
"""Validate that a name does not contain '/' characters.
The '/' character is used as a path separator in web server URLs,
so it cannot be used in entity or device names.
"""
if "/" in value:
raise Invalid(
f"Name cannot contain '/' character (used as URL path separator): {value}"
)
return value
# Maximum length for entity, device, and area names
# This ensures web server URL IDs fit in a 280-byte buffer:
# domain(20) + "/" + device(120) + "/" + name(120) + null = 263 bytes
# Note: Must be < 255 because web_server UrlMatch uses uint8_t for length fields
NAME_MAX_LENGTH = 120
def _validate_entity_name(value):
value = string(value)
try:
@@ -1991,9 +2011,28 @@ def _validate_entity_name(value):
requires_friendly_name(
"Name cannot be None when esphome->friendly_name is not set!"
)(value)
if value is not None:
# Validate length for web server URL compatibility
if len(value) > NAME_MAX_LENGTH:
raise Invalid(
f"Name is too long ({len(value)} chars). "
f"Maximum length is {NAME_MAX_LENGTH} characters."
)
# Validate no '/' in name for web server URL compatibility
_validate_no_slash(value)
return value
def string_no_slash(value):
"""Validate a string that cannot contain '/' characters.
Used for device and area names where '/' is reserved as a URL path separator.
Use with cv.Length() to also enforce maximum length.
"""
value = string(value)
return _validate_no_slash(value)
ENTITY_BASE_SCHEMA = Schema(
{
Optional(CONF_NAME): _validate_entity_name,
+16
View File
@@ -7,6 +7,9 @@
#ifdef USE_ESP8266
#include <pgmspace.h>
#endif
#ifdef USE_ESP32
#include <esp_chip_info.h>
#endif
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <algorithm>
@@ -203,6 +206,19 @@ void Application::loop() {
ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
#ifdef ESPHOME_PROJECT_NAME
ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
#endif
#ifdef USE_ESP32
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
chip_info.revision % 100, chip_info.cores);
#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
// Suggest optimization for chips that don't need the PSRAM cache workaround
if (chip_info.revision >= 300) {
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
chip_info.revision % 100);
}
#endif
#endif
}

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