Merge branch 'zero_copy_str' into integration

This commit is contained in:
J. Nick Koston 2025-07-21 22:26:09 -10:00
commit 922692338f
No known key found for this signature in database
22 changed files with 976 additions and 681 deletions

View File

@ -248,8 +248,10 @@ void APIConnection::loop() {
if (state_subs_at_ < static_cast<int>(subs.size())) { if (state_subs_at_ < static_cast<int>(subs.size())) {
auto &it = subs[state_subs_at_]; auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp; SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id; resp.set_entity_id(StringRef(it.entity_id));
resp.attribute = it.attribute.value(); // attribute.value() returns temporary - must store it
std::string attribute_value = it.attribute.value();
resp.set_attribute(StringRef(attribute_value));
resp.once = it.once; resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) { if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
state_subs_at_++; state_subs_at_++;
@ -260,14 +262,14 @@ void APIConnection::loop() {
} }
} }
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
// remote initiated disconnect_client // remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response // don't close yet, we still need to send the disconnect response
// close will happen on next loop // close will happen on next loop
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
this->flags_.next_close = true; this->flags_.next_close = true;
DisconnectResponse resp; DisconnectResponse resp;
return resp; return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
} }
void APIConnection::on_disconnect_response(const DisconnectResponse &value) { void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
this->helper_->close(); this->helper_->close();
@ -344,7 +346,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
bool is_single) { bool is_single) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity); auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
ListEntitiesBinarySensorResponse msg; ListEntitiesBinarySensorResponse msg;
msg.device_class = binary_sensor->get_device_class(); msg.set_device_class(binary_sensor->get_device_class_ref());
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single); remaining_size, is_single);
@ -376,7 +378,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_position = traits.get_supports_position(); msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt(); msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop(); msg.supports_stop = traits.get_supports_stop();
msg.device_class = cover->get_device_class(); msg.set_device_class(cover->get_device_class_ref());
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@ -411,7 +413,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
if (traits.supports_direction()) if (traits.supports_direction())
msg.direction = static_cast<enums::FanDirection>(fan->direction); msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes()) if (traits.supports_preset_modes())
msg.preset_mode = fan->preset_mode; msg.set_preset_mode(StringRef(fan->preset_mode));
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -468,8 +470,11 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.color_temperature = values.get_color_temperature(); resp.color_temperature = values.get_color_temperature();
resp.cold_white = values.get_cold_white(); resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white(); resp.warm_white = values.get_warm_white();
if (light->supports_effects()) if (light->supports_effects()) {
resp.effect = light->get_effect_name(); // get_effect_name() returns temporary std::string - must store it
std::string effect_name = light->get_effect_name();
resp.set_effect(StringRef(effect_name));
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -545,10 +550,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
bool is_single) { bool is_single) {
auto *sensor = static_cast<sensor::Sensor *>(entity); auto *sensor = static_cast<sensor::Sensor *>(entity);
ListEntitiesSensorResponse msg; ListEntitiesSensorResponse msg;
msg.unit_of_measurement = sensor->get_unit_of_measurement(); msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update(); msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class(); msg.set_device_class(sensor->get_device_class_ref());
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
@ -575,7 +580,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
auto *a_switch = static_cast<switch_::Switch *>(entity); auto *a_switch = static_cast<switch_::Switch *>(entity);
ListEntitiesSwitchResponse msg; ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state(); msg.assumed_state = a_switch->assumed_state();
msg.device_class = a_switch->get_device_class(); msg.set_device_class(a_switch->get_device_class_ref());
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@ -600,7 +605,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
bool is_single) { bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity); auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
TextSensorStateResponse resp; TextSensorStateResponse resp;
resp.state = text_sensor->state; resp.set_state(StringRef(text_sensor->state));
resp.missing_state = !text_sensor->has_state(); resp.missing_state = !text_sensor->has_state();
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
@ -609,7 +614,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
bool is_single) { bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity); auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg; ListEntitiesTextSensorResponse msg;
msg.device_class = text_sensor->get_device_class(); msg.set_device_class(text_sensor->get_device_class_ref());
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
remaining_size, is_single); remaining_size, is_single);
} }
@ -637,13 +642,19 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
} }
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value()); resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
resp.custom_fan_mode = climate->custom_fan_mode.value(); // custom_fan_mode.value() returns temporary - must store it
std::string custom_fan_mode = climate->custom_fan_mode.value();
resp.set_custom_fan_mode(StringRef(custom_fan_mode));
}
if (traits.get_supports_presets() && climate->preset.has_value()) { if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
} }
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
resp.custom_preset = climate->custom_preset.value(); // custom_preset.value() returns temporary - must store it
std::string custom_preset = climate->custom_preset.value();
resp.set_custom_preset(StringRef(custom_preset));
}
if (traits.get_supports_swing_modes()) if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode); resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
if (traits.get_supports_current_humidity()) if (traits.get_supports_current_humidity())
@ -729,9 +740,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
bool is_single) { bool is_single) {
auto *number = static_cast<number::Number *>(entity); auto *number = static_cast<number::Number *>(entity);
ListEntitiesNumberResponse msg; ListEntitiesNumberResponse msg;
msg.unit_of_measurement = number->traits.get_unit_of_measurement(); msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode()); msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.device_class = number->traits.get_device_class(); msg.set_device_class(number->traits.get_device_class_ref());
msg.min_value = number->traits.get_min_value(); msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value(); msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step(); msg.step = number->traits.get_step();
@ -844,7 +855,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
bool is_single) { bool is_single) {
auto *text = static_cast<text::Text *>(entity); auto *text = static_cast<text::Text *>(entity);
TextStateResponse resp; TextStateResponse resp;
resp.state = text->state; resp.set_state(StringRef(text->state));
resp.missing_state = !text->has_state(); resp.missing_state = !text->has_state();
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
@ -856,7 +867,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode()); msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
msg.min_length = text->traits.get_min_length(); msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length(); msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern(); msg.set_pattern(text->traits.get_pattern_ref());
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@ -877,7 +888,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
bool is_single) { bool is_single) {
auto *select = static_cast<select::Select *>(entity); auto *select = static_cast<select::Select *>(entity);
SelectStateResponse resp; SelectStateResponse resp;
resp.state = select->state; resp.set_state(StringRef(select->state));
resp.missing_state = !select->has_state(); resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
@ -903,7 +914,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
bool is_single) { bool is_single) {
auto *button = static_cast<button::Button *>(entity); auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg; ListEntitiesButtonResponse msg;
msg.device_class = button->get_device_class(); msg.set_device_class(button->get_device_class_ref());
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@ -972,7 +983,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
auto *valve = static_cast<valve::Valve *>(entity); auto *valve = static_cast<valve::Valve *>(entity);
ListEntitiesValveResponse msg; ListEntitiesValveResponse msg;
auto traits = valve->get_traits(); auto traits = valve->get_traits();
msg.device_class = valve->get_device_class(); msg.set_device_class(valve->get_device_class_ref());
msg.assumed_state = traits.get_is_assumed_state(); msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position(); msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop(); msg.supports_stop = traits.get_supports_stop();
@ -1014,13 +1025,13 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
auto traits = media_player->get_traits(); auto traits = media_player->get_traits();
msg.supports_pause = traits.get_supports_pause(); msg.supports_pause = traits.get_supports_pause();
for (auto &supported_format : traits.get_supported_formats()) { for (auto &supported_format : traits.get_supported_formats()) {
MediaPlayerSupportedFormat media_format; msg.supported_formats.emplace_back();
media_format.format = supported_format.format; auto &media_format = msg.supported_formats.back();
media_format.set_format(StringRef(supported_format.format));
media_format.sample_rate = supported_format.sample_rate; media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels; media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose); media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
media_format.sample_bytes = supported_format.sample_bytes; media_format.sample_bytes = supported_format.sample_bytes;
msg.supported_formats.push_back(media_format);
} }
return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
remaining_size, is_single); remaining_size, is_single);
@ -1083,6 +1094,12 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
} }
#endif #endif
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
GetTimeResponse resp;
resp.epoch_seconds = ::time(nullptr);
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
}
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) { void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags); bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
@ -1113,12 +1130,12 @@ void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg)
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg); bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
} }
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free( bool APIConnection::send_subscribe_bluetooth_connections_free_response(
const SubscribeBluetoothConnectionsFreeRequest &msg) { const SubscribeBluetoothConnectionsFreeRequest &msg) {
BluetoothConnectionsFreeResponse resp; BluetoothConnectionsFreeResponse resp;
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free(); resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit(); resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
return resp; return this->send_message(resp, BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
} }
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) { void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
@ -1179,28 +1196,27 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
} }
} }
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) {
const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp; VoiceAssistantConfigurationResponse resp;
if (!this->check_voice_assistant_api_connection_()) { if (!this->check_voice_assistant_api_connection_()) {
return resp; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
} }
auto &config = voice_assistant::global_voice_assistant->get_configuration(); auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) { for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word; resp.available_wake_words.emplace_back();
resp_wake_word.id = wake_word.id; auto &resp_wake_word = resp.available_wake_words.back();
resp_wake_word.wake_word = wake_word.wake_word; resp_wake_word.set_id(StringRef(wake_word.id));
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
for (const auto &lang : wake_word.trained_languages) { for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang); resp_wake_word.trained_languages.push_back(lang);
} }
resp.available_wake_words.push_back(std::move(resp_wake_word));
} }
for (auto &wake_word_id : config.active_wake_words) { for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id); resp.active_wake_words.push_back(wake_word_id);
} }
resp.max_active_wake_words = config.max_active_wake_words; resp.max_active_wake_words = config.max_active_wake_words;
return resp; return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
} }
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
@ -1273,7 +1289,7 @@ void APIConnection::send_event(event::Event *event, const std::string &event_typ
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
EventResponse resp; EventResponse resp;
resp.event_type = event_type; resp.set_event_type(StringRef(event_type));
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
@ -1281,7 +1297,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
bool is_single) { bool is_single) {
auto *event = static_cast<event::Event *>(entity); auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg; ListEntitiesEventResponse msg;
msg.device_class = event->get_device_class(); msg.set_device_class(event->get_device_class_ref());
for (const auto &event_type : event->get_event_types()) for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type); msg.event_types.push_back(event_type);
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
@ -1305,11 +1321,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
resp.has_progress = true; resp.has_progress = true;
resp.progress = update->update_info.progress; resp.progress = update->update_info.progress;
} }
resp.current_version = update->update_info.current_version; resp.set_current_version(StringRef(update->update_info.current_version));
resp.latest_version = update->update_info.latest_version; resp.set_latest_version(StringRef(update->update_info.latest_version));
resp.title = update->update_info.title; resp.set_title(StringRef(update->update_info.title));
resp.release_summary = update->update_info.summary; resp.set_release_summary(StringRef(update->update_info.summary));
resp.release_url = update->update_info.release_url; resp.set_release_url(StringRef(update->update_info.release_url));
} }
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
@ -1317,7 +1333,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
bool is_single) { bool is_single) {
auto *update = static_cast<update::UpdateEntity *>(entity); auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg; ListEntitiesUpdateResponse msg;
msg.device_class = update->get_device_class(); msg.set_device_class(update->get_device_class_ref());
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@ -1366,7 +1382,7 @@ void APIConnection::complete_authentication_() {
#endif #endif
} }
HelloResponse APIConnection::hello(const HelloRequest &msg) { bool APIConnection::send_hello_response(const HelloRequest &msg) {
this->client_info_.name = msg.client_info; this->client_info_.name = msg.client_info;
this->client_info_.peername = this->helper_->getpeername(); this->client_info_.peername = this->helper_->getpeername();
this->client_api_version_major_ = msg.api_version_major; this->client_api_version_major_ = msg.api_version_major;
@ -1377,8 +1393,10 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp; HelloResponse resp;
resp.api_version_major = 1; resp.api_version_major = 1;
resp.api_version_minor = 10; resp.api_version_minor = 10;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; // Temporary string for concatenation - will be valid during send_message call
resp.name = App.get_name(); std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.set_server_info(StringRef(server_info));
resp.set_name(StringRef(App.get_name()));
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
// Password required - wait for authentication // Password required - wait for authentication
@ -1388,9 +1406,9 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
this->complete_authentication_(); this->complete_authentication_();
#endif #endif
return resp; return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
} }
ConnectResponse APIConnection::connect(const ConnectRequest &msg) { bool APIConnection::send_connect_response(const ConnectRequest &msg) {
bool correct = true; bool correct = true;
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
correct = this->parent_->check_password(msg.password); correct = this->parent_->check_password(msg.password);
@ -1402,48 +1420,71 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
if (correct) { if (correct) {
this->complete_authentication_(); this->complete_authentication_();
} }
return resp; return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
} }
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
bool APIConnection::send_ping_response(const PingRequest &msg) {
PingResponse resp;
return this->send_message(resp, PingResponse::MESSAGE_TYPE);
}
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
DeviceInfoResponse resp{}; DeviceInfoResponse resp{};
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
resp.uses_password = true; resp.uses_password = true;
#endif #endif
resp.name = App.get_name(); resp.set_name(StringRef(App.get_name()));
resp.friendly_name = App.get_friendly_name(); resp.set_friendly_name(StringRef(App.get_friendly_name()));
#ifdef USE_AREAS #ifdef USE_AREAS
resp.suggested_area = App.get_area(); resp.set_suggested_area(StringRef(App.get_area()));
#endif #endif
resp.mac_address = get_mac_address_pretty(); // mac_address must store temporary string - will be valid during send_message call
resp.esphome_version = ESPHOME_VERSION; std::string mac_address = get_mac_address_pretty();
resp.compilation_time = App.get_compilation_time(); resp.set_mac_address(StringRef(mac_address));
// Compile-time StringRef constants
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF);
// get_compilation_time() returns temporary std::string - must store it
std::string compilation_time = App.get_compilation_time();
resp.set_compilation_time(StringRef(compilation_time));
// Compile-time StringRef constants for manufacturers
#if defined(USE_ESP8266) || defined(USE_ESP32) #if defined(USE_ESP8266) || defined(USE_ESP32)
resp.manufacturer = "Espressif"; static constexpr auto MANUFACTURER = StringRef::from_lit("Espressif");
#elif defined(USE_RP2040) #elif defined(USE_RP2040)
resp.manufacturer = "Raspberry Pi"; static constexpr auto MANUFACTURER = StringRef::from_lit("Raspberry Pi");
#elif defined(USE_BK72XX) #elif defined(USE_BK72XX)
resp.manufacturer = "Beken"; static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
#elif defined(USE_LN882X) #elif defined(USE_LN882X)
resp.manufacturer = "Lightning"; static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
#elif defined(USE_RTL87XX) #elif defined(USE_RTL87XX)
resp.manufacturer = "Realtek"; static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
#elif defined(USE_HOST) #elif defined(USE_HOST)
resp.manufacturer = "Host"; static constexpr auto MANUFACTURER = StringRef::from_lit("Host");
#endif #endif
resp.model = ESPHOME_BOARD; resp.set_manufacturer(MANUFACTURER);
static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
resp.set_model(MODEL);
#ifdef USE_DEEP_SLEEP #ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep; resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
#endif #endif
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME; static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
resp.project_version = ESPHOME_PROJECT_VERSION; static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
resp.set_project_name(PROJECT_NAME);
resp.set_project_version(PROJECT_VERSION);
#endif #endif
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
resp.webserver_port = USE_WEBSERVER_PORT; resp.webserver_port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(); // bt_mac must store temporary string - will be valid during send_message call
std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags(); resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
@ -1453,23 +1494,25 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#endif #endif
#ifdef USE_DEVICES #ifdef USE_DEVICES
for (auto const &device : App.get_devices()) { for (auto const &device : App.get_devices()) {
DeviceInfo device_info; resp.devices.emplace_back();
auto &device_info = resp.devices.back();
device_info.device_id = device->get_device_id(); device_info.device_id = device->get_device_id();
device_info.name = device->get_name(); device_info.set_name(StringRef(device->get_name()));
device_info.area_id = device->get_area_id(); device_info.area_id = device->get_area_id();
resp.devices.push_back(device_info);
} }
#endif #endif
#ifdef USE_AREAS #ifdef USE_AREAS
for (auto const &area : App.get_areas()) { for (auto const &area : App.get_areas()) {
AreaInfo area_info; resp.areas.emplace_back();
auto &area_info = resp.areas.back();
area_info.area_id = area->get_area_id(); area_info.area_id = area->get_area_id();
area_info.name = area->get_name(); area_info.set_name(StringRef(area->get_name()));
resp.areas.push_back(area_info);
} }
#endif #endif
return resp;
return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
} }
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs()) { for (auto &it : this->parent_->get_state_subs()) {
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) { if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
@ -1491,23 +1534,21 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
} }
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{}; psk_t psk{};
NoiseEncryptionSetKeyResponse resp; NoiseEncryptionSetKeyResponse resp;
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length"); ESP_LOGW(TAG, "Invalid encryption key length");
resp.success = false; resp.success = false;
return resp; return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
} }
if (!this->parent_->save_noise_psk(psk, true)) { if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key"); ESP_LOGW(TAG, "Failed to save encryption key");
resp.success = false; resp.success = false;
return resp; return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
} }
resp.success = true; resp.success = true;
return resp; return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
} }
#endif #endif
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) { void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {

View File

@ -148,8 +148,7 @@ class APIConnection : public APIServerConnection {
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override; void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override; void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override; void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( bool send_subscribe_bluetooth_connections_free_response(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override; void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
#endif #endif
@ -167,8 +166,7 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
VoiceAssistantConfigurationResponse voice_assistant_get_configuration( bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override;
const VoiceAssistantConfigurationRequest &msg) override;
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif #endif
@ -195,11 +193,11 @@ class APIConnection : public APIServerConnection {
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void on_get_time_response(const GetTimeResponse &value) override; void on_get_time_response(const GetTimeResponse &value) override;
#endif #endif
HelloResponse hello(const HelloRequest &msg) override; bool send_hello_response(const HelloRequest &msg) override;
ConnectResponse connect(const ConnectRequest &msg) override; bool send_connect_response(const ConnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override; bool send_disconnect_response(const DisconnectRequest &msg) override;
PingResponse ping(const PingRequest &msg) override { return {}; } bool send_ping_response(const PingRequest &msg) override;
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; bool send_device_info_response(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
void subscribe_states(const SubscribeStatesRequest &msg) override { void subscribe_states(const SubscribeStatesRequest &msg) override {
this->flags_.state_subscription = true; this->flags_.state_subscription = true;
@ -214,15 +212,12 @@ class APIConnection : public APIServerConnection {
this->flags_.service_call_subscription = true; this->flags_.service_call_subscription = true;
} }
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
GetTimeResponse get_time(const GetTimeRequest &msg) override { bool send_get_time_response(const GetTimeRequest &msg) override;
// TODO
return {};
}
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override; void execute_service(const ExecuteServiceRequest &msg) override;
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif
bool is_authenticated() override { bool is_authenticated() override {
@ -313,14 +308,17 @@ class APIConnection : public APIServerConnection {
APIConnection *conn, uint32_t remaining_size, bool is_single) { APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types // Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash(); msg.key = entity->get_object_id_hash();
msg.object_id = entity->get_object_id(); // IMPORTANT: get_object_id() may return a temporary std::string
std::string object_id = entity->get_object_id();
msg.set_object_id(StringRef(object_id));
if (entity->has_own_name()) if (entity->has_own_name()) {
msg.name = entity->get_name(); msg.set_name(entity->get_name());
}
// Set common EntityBase properties // Set common EntityBase properties
#ifdef USE_ENTITY_ICON #ifdef USE_ENTITY_ICON
msg.icon = entity->get_icon(); msg.set_icon(entity->get_icon_ref());
#endif #endif
msg.disabled_by_default = entity->is_disabled_by_default(); msg.disabled_by_default = entity->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/string_ref.h"
#include "proto.h" #include "proto.h"
@ -269,12 +270,15 @@ enum UpdateCommand : uint32_t {
class InfoResponseProtoMessage : public ProtoMessage { class InfoResponseProtoMessage : public ProtoMessage {
public: public:
~InfoResponseProtoMessage() override = default; ~InfoResponseProtoMessage() override = default;
std::string object_id{}; StringRef object_id_ref_{};
void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; }
uint32_t key{0}; uint32_t key{0};
std::string name{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
bool disabled_by_default{false}; bool disabled_by_default{false};
#ifdef USE_ENTITY_ICON #ifdef USE_ENTITY_ICON
std::string icon{}; StringRef icon_ref_{};
void set_icon(const StringRef &ref) { this->icon_ref_ = ref; }
#endif #endif
enums::EntityCategory entity_category{}; enums::EntityCategory entity_category{};
#ifdef USE_DEVICES #ifdef USE_DEVICES
@ -332,8 +336,10 @@ class HelloResponse : public ProtoMessage {
#endif #endif
uint32_t api_version_major{0}; uint32_t api_version_major{0};
uint32_t api_version_minor{0}; uint32_t api_version_minor{0};
std::string server_info{}; StringRef server_info_ref_{};
std::string name{}; void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; }
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -442,7 +448,8 @@ class DeviceInfoRequest : public ProtoDecodableMessage {
class AreaInfo : public ProtoMessage { class AreaInfo : public ProtoMessage {
public: public:
uint32_t area_id{0}; uint32_t area_id{0};
std::string name{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -456,7 +463,8 @@ class AreaInfo : public ProtoMessage {
class DeviceInfo : public ProtoMessage { class DeviceInfo : public ProtoMessage {
public: public:
uint32_t device_id{0}; uint32_t device_id{0};
std::string name{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t area_id{0}; uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -477,19 +485,26 @@ class DeviceInfoResponse : public ProtoMessage {
#ifdef USE_API_PASSWORD #ifdef USE_API_PASSWORD
bool uses_password{false}; bool uses_password{false};
#endif #endif
std::string name{}; StringRef name_ref_{};
std::string mac_address{}; void set_name(const StringRef &ref) { this->name_ref_ = ref; }
std::string esphome_version{}; StringRef mac_address_ref_{};
std::string compilation_time{}; void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; }
std::string model{}; StringRef esphome_version_ref_{};
void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; }
StringRef compilation_time_ref_{};
void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; }
StringRef model_ref_{};
void set_model(const StringRef &ref) { this->model_ref_ = ref; }
#ifdef USE_DEEP_SLEEP #ifdef USE_DEEP_SLEEP
bool has_deep_sleep{false}; bool has_deep_sleep{false};
#endif #endif
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
std::string project_name{}; StringRef project_name_ref_{};
void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; }
#endif #endif
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
std::string project_version{}; StringRef project_version_ref_{};
void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; }
#endif #endif
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
uint32_t webserver_port{0}; uint32_t webserver_port{0};
@ -497,16 +512,20 @@ class DeviceInfoResponse : public ProtoMessage {
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
uint32_t bluetooth_proxy_feature_flags{0}; uint32_t bluetooth_proxy_feature_flags{0};
#endif #endif
std::string manufacturer{}; StringRef manufacturer_ref_{};
std::string friendly_name{}; void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; }
StringRef friendly_name_ref_{};
void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; }
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
uint32_t voice_assistant_feature_flags{0}; uint32_t voice_assistant_feature_flags{0};
#endif #endif
#ifdef USE_AREAS #ifdef USE_AREAS
std::string suggested_area{}; StringRef suggested_area_ref_{};
void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
std::string bluetooth_mac_address{}; StringRef bluetooth_mac_address_ref_{};
void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; }
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
bool api_encryption_supported{false}; bool api_encryption_supported{false};
@ -575,7 +594,8 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_binary_sensor_response"; } const char *message_name() const override { return "list_entities_binary_sensor_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool is_status_binary_sensor{false}; bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -614,7 +634,8 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
bool assumed_state{false}; bool assumed_state{false};
bool supports_position{false}; bool supports_position{false};
bool supports_tilt{false}; bool supports_tilt{false};
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool supports_stop{false}; bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -695,7 +716,8 @@ class FanStateResponse : public StateResponseProtoMessage {
bool oscillating{false}; bool oscillating{false};
enums::FanDirection direction{}; enums::FanDirection direction{};
int32_t speed_level{0}; int32_t speed_level{0};
std::string preset_mode{}; StringRef preset_mode_ref_{};
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -769,7 +791,8 @@ class LightStateResponse : public StateResponseProtoMessage {
float color_temperature{0.0f}; float color_temperature{0.0f};
float cold_white{0.0f}; float cold_white{0.0f};
float warm_white{0.0f}; float warm_white{0.0f};
std::string effect{}; StringRef effect_ref_{};
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -829,10 +852,12 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_sensor_response"; } const char *message_name() const override { return "list_entities_sensor_response"; }
#endif #endif
std::string unit_of_measurement{}; StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
int32_t accuracy_decimals{0}; int32_t accuracy_decimals{0};
bool force_update{false}; bool force_update{false};
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
enums::SensorStateClass state_class{}; enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -869,7 +894,8 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_switch_response"; } const char *message_name() const override { return "list_entities_switch_response"; }
#endif #endif
bool assumed_state{false}; bool assumed_state{false};
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -919,7 +945,8 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_text_sensor_response"; } const char *message_name() const override { return "list_entities_text_sensor_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -935,7 +962,8 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_sensor_state_response"; } const char *message_name() const override { return "text_sensor_state_response"; }
#endif #endif
std::string state{}; StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -1032,8 +1060,10 @@ class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage {
}; };
class HomeassistantServiceMap : public ProtoMessage { class HomeassistantServiceMap : public ProtoMessage {
public: public:
std::string key{}; StringRef key_ref_{};
std::string value{}; void set_key(const StringRef &ref) { this->key_ref_ = ref; }
StringRef value_ref_{};
void set_value(const StringRef &ref) { this->value_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -1049,7 +1079,8 @@ class HomeassistantServiceResponse : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_service_response"; } const char *message_name() const override { return "homeassistant_service_response"; }
#endif #endif
std::string service{}; StringRef service_ref_{};
void set_service(const StringRef &ref) { this->service_ref_ = ref; }
std::vector<HomeassistantServiceMap> data{}; std::vector<HomeassistantServiceMap> data{};
std::vector<HomeassistantServiceMap> data_template{}; std::vector<HomeassistantServiceMap> data_template{};
std::vector<HomeassistantServiceMap> variables{}; std::vector<HomeassistantServiceMap> variables{};
@ -1082,8 +1113,10 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_home_assistant_state_response"; } const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
#endif #endif
std::string entity_id{}; StringRef entity_id_ref_{};
std::string attribute{}; void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; }
StringRef attribute_ref_{};
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
bool once{false}; bool once{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -1143,7 +1176,8 @@ class GetTimeResponse : public ProtoDecodableMessage {
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
class ListEntitiesServicesArgument : public ProtoMessage { class ListEntitiesServicesArgument : public ProtoMessage {
public: public:
std::string name{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
enums::ServiceArgType type{}; enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -1160,7 +1194,8 @@ class ListEntitiesServicesResponse : public ProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_services_response"; } const char *message_name() const override { return "list_entities_services_response"; }
#endif #endif
std::string name{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t key{0}; uint32_t key{0};
std::vector<ListEntitiesServicesArgument> args{}; std::vector<ListEntitiesServicesArgument> args{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
@ -1312,9 +1347,11 @@ class ClimateStateResponse : public StateResponseProtoMessage {
enums::ClimateAction action{}; enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{}; enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{}; enums::ClimateSwingMode swing_mode{};
std::string custom_fan_mode{}; StringRef custom_fan_mode_ref_{};
void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; }
enums::ClimatePreset preset{}; enums::ClimatePreset preset{};
std::string custom_preset{}; StringRef custom_preset_ref_{};
void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; }
float current_humidity{0.0f}; float current_humidity{0.0f};
float target_humidity{0.0f}; float target_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
@ -1373,9 +1410,11 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
float min_value{0.0f}; float min_value{0.0f};
float max_value{0.0f}; float max_value{0.0f};
float step{0.0f}; float step{0.0f};
std::string unit_of_measurement{}; StringRef unit_of_measurement_ref_{};
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
enums::NumberMode mode{}; enums::NumberMode mode{};
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -1442,7 +1481,8 @@ class SelectStateResponse : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "select_state_response"; } const char *message_name() const override { return "select_state_response"; }
#endif #endif
std::string state{}; StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -1541,7 +1581,8 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage {
bool assumed_state{false}; bool assumed_state{false};
bool supports_open{false}; bool supports_open{false};
bool requires_code{false}; bool requires_code{false};
std::string code_format{}; StringRef code_format_ref_{};
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -1594,7 +1635,8 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_button_response"; } const char *message_name() const override { return "list_entities_button_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -1622,7 +1664,8 @@ class ButtonCommandRequest : public CommandProtoMessage {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
class MediaPlayerSupportedFormat : public ProtoMessage { class MediaPlayerSupportedFormat : public ProtoMessage {
public: public:
std::string format{}; StringRef format_ref_{};
void set_format(const StringRef &ref) { this->format_ref_ = ref; }
uint32_t sample_rate{0}; uint32_t sample_rate{0};
uint32_t num_channels{0}; uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{}; enums::MediaPlayerFormatPurpose purpose{};
@ -2219,10 +2262,12 @@ class VoiceAssistantRequest : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_request"; } const char *message_name() const override { return "voice_assistant_request"; }
#endif #endif
bool start{false}; bool start{false};
std::string conversation_id{}; StringRef conversation_id_ref_{};
void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; }
uint32_t flags{0}; uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{}; VoiceAssistantAudioSettings audio_settings{};
std::string wake_word_phrase{}; StringRef wake_word_phrase_ref_{};
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -2358,8 +2403,10 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage {
}; };
class VoiceAssistantWakeWord : public ProtoMessage { class VoiceAssistantWakeWord : public ProtoMessage {
public: public:
std::string id{}; StringRef id_ref_{};
std::string wake_word{}; void set_id(const StringRef &ref) { this->id_ref_ = ref; }
StringRef wake_word_ref_{};
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
std::vector<std::string> trained_languages{}; std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -2480,7 +2527,8 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage {
#endif #endif
uint32_t min_length{0}; uint32_t min_length{0};
uint32_t max_length{0}; uint32_t max_length{0};
std::string pattern{}; StringRef pattern_ref_{};
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
enums::TextMode mode{}; enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -2497,7 +2545,8 @@ class TextStateResponse : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "text_state_response"; } const char *message_name() const override { return "text_state_response"; }
#endif #endif
std::string state{}; StringRef state_ref_{};
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -2641,7 +2690,8 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_event_response"; } const char *message_name() const override { return "list_entities_event_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
std::vector<std::string> event_types{}; std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
@ -2658,7 +2708,8 @@ class EventResponse : public StateResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "event_response"; } const char *message_name() const override { return "event_response"; }
#endif #endif
std::string event_type{}; StringRef event_type_ref_{};
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -2676,7 +2727,8 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_valve_response"; } const char *message_name() const override { return "list_entities_valve_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool assumed_state{false}; bool assumed_state{false};
bool supports_position{false}; bool supports_position{false};
bool supports_stop{false}; bool supports_stop{false};
@ -2782,7 +2834,8 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_update_response"; } const char *message_name() const override { return "list_entities_update_response"; }
#endif #endif
std::string device_class{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -2802,11 +2855,16 @@ class UpdateStateResponse : public StateResponseProtoMessage {
bool in_progress{false}; bool in_progress{false};
bool has_progress{false}; bool has_progress{false};
float progress{0.0f}; float progress{0.0f};
std::string current_version{}; StringRef current_version_ref_{};
std::string latest_version{}; void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; }
std::string title{}; StringRef latest_version_ref_{};
std::string release_summary{}; void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; }
std::string release_url{}; StringRef title_ref_{};
void set_title(const StringRef &ref) { this->title_ref_ = ref; }
StringRef release_summary_ref_{};
void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; }
StringRef release_url_ref_{};
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP

File diff suppressed because it is too large Load Diff

View File

@ -597,33 +597,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
void APIServerConnection::on_hello_request(const HelloRequest &msg) { void APIServerConnection::on_hello_request(const HelloRequest &msg) {
HelloResponse ret = this->hello(msg); if (!this->send_hello_response(msg)) {
if (!this->send_message(ret, HelloResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_connect_request(const ConnectRequest &msg) { void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
ConnectResponse ret = this->connect(msg); if (!this->send_connect_response(msg)) {
if (!this->send_message(ret, ConnectResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) { void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
DisconnectResponse ret = this->disconnect(msg); if (!this->send_disconnect_response(msg)) {
if (!this->send_message(ret, DisconnectResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_ping_request(const PingRequest &msg) { void APIServerConnection::on_ping_request(const PingRequest &msg) {
PingResponse ret = this->ping(msg); if (!this->send_ping_response(msg)) {
if (!this->send_message(ret, PingResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
if (this->check_connection_setup_()) { if (this->check_connection_setup_()) {
DeviceInfoResponse ret = this->device_info(msg); if (!this->send_device_info_response(msg)) {
if (!this->send_message(ret, DeviceInfoResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
@ -656,8 +651,7 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
} }
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
if (this->check_connection_setup_()) { if (this->check_connection_setup_()) {
GetTimeResponse ret = this->get_time(msg); if (!this->send_get_time_response(msg)) {
if (!this->send_message(ret, GetTimeResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
@ -672,8 +666,7 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg); if (!this->send_noise_encryption_set_key_response(msg)) {
if (!this->send_message(ret, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
@ -866,8 +859,7 @@ void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNo
void APIServerConnection::on_subscribe_bluetooth_connections_free_request( void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
const SubscribeBluetoothConnectionsFreeRequest &msg) { const SubscribeBluetoothConnectionsFreeRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); if (!this->send_subscribe_bluetooth_connections_free_response(msg)) {
if (!this->send_message(ret, BluetoothConnectionsFreeResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }
@ -898,8 +890,7 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); if (!this->send_voice_assistant_get_configuration_response(msg)) {
if (!this->send_message(ret, VoiceAssistantConfigurationResponse::MESSAGE_TYPE)) {
this->on_fatal_error(); this->on_fatal_error();
} }
} }

View File

@ -207,22 +207,22 @@ class APIServerConnectionBase : public ProtoService {
class APIServerConnection : public APIServerConnectionBase { class APIServerConnection : public APIServerConnectionBase {
public: public:
virtual HelloResponse hello(const HelloRequest &msg) = 0; virtual bool send_hello_response(const HelloRequest &msg) = 0;
virtual ConnectResponse connect(const ConnectRequest &msg) = 0; virtual bool send_connect_response(const ConnectRequest &msg) = 0;
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0; virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
virtual PingResponse ping(const PingRequest &msg) = 0; virtual bool send_ping_response(const PingRequest &msg) = 0;
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0; virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
virtual void list_entities(const ListEntitiesRequest &msg) = 0; virtual void list_entities(const ListEntitiesRequest &msg) = 0;
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0; virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0; virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0; virtual void button_command(const ButtonCommandRequest &msg) = 0;
@ -303,7 +303,7 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0; virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free( virtual bool send_subscribe_bluetooth_connections_free_response(
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0; const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
@ -316,8 +316,7 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration( virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0;
const VoiceAssistantConfigurationRequest &msg) = 0;
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;

View File

@ -148,7 +148,7 @@ class CustomAPIDevice {
*/ */
void call_homeassistant_service(const std::string &service_name) { void call_homeassistant_service(const std::string &service_name) {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = service_name; resp.set_service(StringRef(service_name));
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }
@ -168,12 +168,12 @@ class CustomAPIDevice {
*/ */
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) { void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = service_name; resp.set_service(StringRef(service_name));
for (auto &it : data) { for (auto &it : data) {
HomeassistantServiceMap kv; resp.data.emplace_back();
kv.key = it.first; auto &kv = resp.data.back();
kv.value = it.second; kv.set_key(StringRef(it.first));
resp.data.push_back(kv); kv.set_value(StringRef(it.second));
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }
@ -190,7 +190,7 @@ class CustomAPIDevice {
*/ */
void fire_homeassistant_event(const std::string &event_name) { void fire_homeassistant_event(const std::string &event_name) {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = event_name; resp.set_service(StringRef(event_name));
resp.is_event = true; resp.is_event = true;
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }
@ -210,13 +210,13 @@ class CustomAPIDevice {
*/ */
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) { void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = service_name; resp.set_service(StringRef(service_name));
resp.is_event = true; resp.is_event = true;
for (auto &it : data) { for (auto &it : data) {
HomeassistantServiceMap kv; resp.data.emplace_back();
kv.key = it.first; auto &kv = resp.data.back();
kv.value = it.second; kv.set_key(StringRef(it.first));
resp.data.push_back(kv); kv.set_value(StringRef(it.second));
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }

View File

@ -59,25 +59,29 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void play(Ts... x) override { void play(Ts... x) override {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = this->service_.value(x...); std::string service_value = this->service_.value(x...);
resp.set_service(StringRef(service_value));
resp.is_event = this->is_event_; resp.is_event = this->is_event_;
for (auto &it : this->data_) { for (auto &it : this->data_) {
HomeassistantServiceMap kv; resp.data.emplace_back();
kv.key = it.key; auto &kv = resp.data.back();
kv.value = it.value.value(x...); kv.set_key(StringRef(it.key));
resp.data.push_back(kv); std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->data_template_) { for (auto &it : this->data_template_) {
HomeassistantServiceMap kv; resp.data_template.emplace_back();
kv.key = it.key; auto &kv = resp.data_template.back();
kv.value = it.value.value(x...); kv.set_key(StringRef(it.key));
resp.data_template.push_back(kv); std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->variables_) { for (auto &it : this->variables_) {
HomeassistantServiceMap kv; resp.variables.emplace_back();
kv.key = it.key; auto &kv = resp.variables.back();
kv.value = it.value.value(x...); kv.set_key(StringRef(it.key));
resp.variables.push_back(kv); std::string value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
this->parent_->send_homeassistant_service_call(resp); this->parent_->send_homeassistant_service_call(resp);
} }

View File

@ -3,6 +3,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
@ -15,6 +16,37 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
/*
* StringRef Ownership Model for API Protocol Messages
* ===================================================
*
* StringRef is used for zero-copy string handling in outgoing (SOURCE_SERVER) messages.
* It holds a pointer and length to existing string data without copying.
*
* CRITICAL: The referenced string data MUST remain valid until message encoding completes.
*
* Safe StringRef Patterns:
* 1. String literals: StringRef("literal") - Always safe (static storage duration)
* 2. Member variables: StringRef(this->member_string_) - Safe if object outlives encoding
* 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe
* 4. Local variables: Safe ONLY if encoding happens before function returns:
* std::string temp = compute_value();
* msg.set_field(StringRef(temp));
* return this->send_message(msg); // temp is valid during encoding
*
* Unsafe Patterns (WILL cause crashes/corruption):
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
* 2. Optional values: msg.set_field(StringRef(optional.value())) // value() returns a copy
* 3. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
*
* For unsafe patterns, store in a local variable first:
* std::string temp = optional.value(); // or get_string() or str1 + str2
* msg.set_field(StringRef(temp));
*
* The send_*_response pattern ensures proper lifetime management by encoding
* within the same function scope where temporaries are created.
*/
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit /// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
class ProtoVarInt { class ProtoVarInt {
public: public:
@ -218,6 +250,9 @@ class ProtoWriteBuffer {
void encode_string(uint32_t field_id, const std::string &value, bool force = false) { void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
this->encode_string(field_id, value.data(), value.size(), force); this->encode_string(field_id, value.data(), value.size(), force);
} }
void encode_string(uint32_t field_id, const StringRef &ref, bool force = false) {
this->encode_string(field_id, ref.c_str(), ref.size(), force);
}
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force); this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
} }
@ -669,17 +704,16 @@ class ProtoSize {
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems // sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
/** /**
* @brief Calculates and adds the size of a string/bytes field to the total message size * @brief Calculates and adds the size of a string field using length
*/ */
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) { static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if string is empty // Skip calculation if string is empty
if (str.empty()) { if (len == 0) {
return; // No need to update total_size return; // No need to update total_size
} }
// Calculate and directly add to total_size // Field ID + length varint + string bytes
const uint32_t str_size = static_cast<uint32_t>(str.size()); total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
total_size += field_id_size + varint(str_size) + str_size;
} }
/** /**

View File

@ -33,14 +33,14 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
ListEntitiesServicesResponse encode_list_service_response() override { ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg; ListEntitiesServicesResponse msg;
msg.name = this->name_; msg.set_name(StringRef(this->name_));
msg.key = this->key_; msg.key = this->key_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...}; std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
for (int i = 0; i < sizeof...(Ts); i++) { for (int i = 0; i < sizeof...(Ts); i++) {
ListEntitiesServicesArgument arg; msg.args.emplace_back();
auto &arg = msg.args.back();
arg.type = arg_types[i]; arg.type = arg_types[i];
arg.name = this->arg_names_[i]; arg.set_name(StringRef(this->arg_names_[i]));
msg.args.push_back(arg);
} }
return msg; return msg;
} }

View File

@ -30,7 +30,7 @@ from esphome.const import (
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
) )
from esphome.core import CORE from esphome.core import CORE, coroutine_with_priority
from esphome.enum import StrEnum from esphome.enum import StrEnum
from esphome.types import ConfigType from esphome.types import ConfigType
@ -365,14 +365,22 @@ async def to_code(config):
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT") cg.add_define("USE_ESP32_BLE_CLIENT")
# Add feature-specific defines based on what's needed CORE.add_job(_add_ble_features)
if BLEFeatures.ESP_BT_DEVICE in _required_features:
cg.add_define("USE_ESP32_BLE_DEVICE")
if config.get(CONF_SOFTWARE_COEXISTENCE): if config.get(CONF_SOFTWARE_COEXISTENCE):
cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE") cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE")
# This needs to be run as a job with very low priority so that all components have
# chance to call register_ble_tracker and register_client before the list is checked
# and added to the global defines list.
@coroutine_with_priority(-1000)
async def _add_ble_features():
# Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features:
cg.add_define("USE_ESP32_BLE_DEVICE")
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.use_id(ESP32BLETracker), cv.GenerateID(): cv.use_id(ESP32BLETracker),

View File

@ -83,18 +83,24 @@ void HomeassistantNumber::control(float value) {
this->publish_state(value); this->publish_state(value);
static constexpr auto SERVICE_NAME = StringRef::from_lit("number.set_value");
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
static constexpr auto VALUE_KEY = StringRef::from_lit("value");
api::HomeassistantServiceResponse resp; api::HomeassistantServiceResponse resp;
resp.service = "number.set_value"; resp.set_service(SERVICE_NAME);
api::HomeassistantServiceMap entity_id; resp.data.emplace_back();
entity_id.key = "entity_id"; auto &entity_id = resp.data.back();
entity_id.value = this->entity_id_; entity_id.set_key(ENTITY_ID_KEY);
resp.data.push_back(entity_id); entity_id.set_value(StringRef(this->entity_id_));
api::HomeassistantServiceMap entity_value; resp.data.emplace_back();
entity_value.key = "value"; auto &entity_value = resp.data.back();
entity_value.value = to_string(value); entity_value.set_key(VALUE_KEY);
resp.data.push_back(entity_value); // to_string() returns a temporary - must store it to avoid dangling reference
std::string value_str = to_string(value);
entity_value.set_value(StringRef(value_str));
api::global_api_server->send_homeassistant_service_call(resp); api::global_api_server->send_homeassistant_service_call(resp);
} }

View File

@ -40,17 +40,21 @@ void HomeassistantSwitch::write_state(bool state) {
return; return;
} }
static constexpr auto SERVICE_ON = StringRef::from_lit("homeassistant.turn_on");
static constexpr auto SERVICE_OFF = StringRef::from_lit("homeassistant.turn_off");
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
api::HomeassistantServiceResponse resp; api::HomeassistantServiceResponse resp;
if (state) { if (state) {
resp.service = "homeassistant.turn_on"; resp.set_service(SERVICE_ON);
} else { } else {
resp.service = "homeassistant.turn_off"; resp.set_service(SERVICE_OFF);
} }
api::HomeassistantServiceMap entity_id_kv; resp.data.emplace_back();
entity_id_kv.key = "entity_id"; auto &entity_id_kv = resp.data.back();
entity_id_kv.value = this->entity_id_; entity_id_kv.set_key(ENTITY_ID_KEY);
resp.data.push_back(entity_id_kv); entity_id_kv.set_value(StringRef(this->entity_id_));
api::global_api_server->send_homeassistant_service_call(resp); api::global_api_server->send_homeassistant_service_call(resp);
} }

View File

@ -23,6 +23,7 @@ from esphome.const import (
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_NRF52, PLATFORM_NRF52,
CoreModel,
) )
from esphome.core import CORE, EsphomeError, coroutine_with_priority from esphome.core import CORE, EsphomeError, coroutine_with_priority
from esphome.storage_json import StorageJSON from esphome.storage_json import StorageJSON
@ -108,6 +109,8 @@ async def to_code(config: ConfigType) -> None:
cg.add_build_flag("-DUSE_NRF52") cg.add_build_flag("-DUSE_NRF52")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "NRF52") cg.add_define("ESPHOME_VARIANT", "NRF52")
# nRF52 processors are single-core
cg.add_define(CoreModel.SINGLE)
cg.add_platformio_option(CONF_FRAMEWORK, CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK]) cg.add_platformio_option(CONF_FRAMEWORK, CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK])
cg.add_platformio_option( cg.add_platformio_option(
"platform", "platform",

View File

@ -3,6 +3,7 @@
#include <utility> #include <utility>
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
namespace esphome { namespace esphome {
namespace text { namespace text {
@ -23,6 +24,7 @@ class TextTraits {
// Set/get the pattern. // Set/get the pattern.
void set_pattern(std::string pattern) { this->pattern_ = std::move(pattern); } void set_pattern(std::string pattern) { this->pattern_ = std::move(pattern); }
std::string get_pattern() const { return this->pattern_; } std::string get_pattern() const { return this->pattern_; }
StringRef get_pattern_ref() const { return StringRef(this->pattern_); }
// Set/get the frontend mode. // Set/get the frontend mode.
void set_mode(TextMode mode) { this->mode_ = mode; } void set_mode(TextMode mode) { this->mode_ = mode; }

View File

@ -73,6 +73,7 @@ from esphome.const import (
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
VALID_SUBSTITUTIONS_CHARACTERS, VALID_SUBSTITUTIONS_CHARACTERS,
Framework,
__version__ as ESPHOME_VERSION, __version__ as ESPHOME_VERSION,
) )
from esphome.core import ( from esphome.core import (
@ -282,6 +283,38 @@ class FinalExternalInvalid(Invalid):
"""Represents an invalid value in the final validation phase where the path should not be prepended.""" """Represents an invalid value in the final validation phase where the path should not be prepended."""
@dataclass(frozen=True, order=True)
class Version:
major: int
minor: int
patch: int
extra: str = ""
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
@classmethod
def parse(cls, value: str) -> Version:
match = re.match(r"^(\d+).(\d+).(\d+)-?(\w*)$", value)
if match is None:
raise ValueError(f"Not a valid version number {value}")
major = int(match[1])
minor = int(match[2])
patch = int(match[3])
extra = match[4] or ""
return Version(major=major, minor=minor, patch=patch, extra=extra)
@property
def is_beta(self) -> bool:
"""Check if this version is a beta version."""
return self.extra.startswith("b")
@property
def is_dev(self) -> bool:
"""Check if this version is a development version."""
return self.extra.startswith("dev")
def check_not_templatable(value): def check_not_templatable(value):
if isinstance(value, Lambda): if isinstance(value, Lambda):
raise Invalid("This option is not templatable!") raise Invalid("This option is not templatable!")
@ -619,16 +652,35 @@ def only_on(platforms):
return validator_ return validator_
def only_with_framework(frameworks): def only_with_framework(
frameworks: Framework | str | list[Framework | str], suggestions=None
):
"""Validate that this option can only be specified on the given frameworks.""" """Validate that this option can only be specified on the given frameworks."""
if not isinstance(frameworks, list): if not isinstance(frameworks, list):
frameworks = [frameworks] frameworks = [frameworks]
frameworks = [Framework(framework) for framework in frameworks]
if suggestions is None:
suggestions = {}
version = Version.parse(ESPHOME_VERSION)
if version.is_beta:
docs_format = "https://beta.esphome.io/components/{path}"
elif version.is_dev:
docs_format = "https://next.esphome.io/components/{path}"
else:
docs_format = "https://esphome.io/components/{path}"
def validator_(obj): def validator_(obj):
if CORE.target_framework not in frameworks: if CORE.target_framework not in frameworks:
raise Invalid( err_str = f"This feature is only available with framework(s) {', '.join([framework.value for framework in frameworks])}"
f"This feature is only available with frameworks {frameworks}" if suggestion := suggestions.get(CORE.target_framework, None):
) (component, docs_path) = suggestion
err_str += f"\nPlease use '{component}'"
if docs_path:
err_str += f": {docs_format.format(path=docs_path)}"
raise Invalid(err_str)
return obj return obj
return validator_ return validator_
@ -637,8 +689,8 @@ def only_with_framework(frameworks):
only_on_esp32 = only_on(PLATFORM_ESP32) only_on_esp32 = only_on(PLATFORM_ESP32)
only_on_esp8266 = only_on(PLATFORM_ESP8266) only_on_esp8266 = only_on(PLATFORM_ESP8266)
only_on_rp2040 = only_on(PLATFORM_RP2040) only_on_rp2040 = only_on(PLATFORM_RP2040)
only_with_arduino = only_with_framework("arduino") only_with_arduino = only_with_framework(Framework.ARDUINO)
only_with_esp_idf = only_with_framework("esp-idf") only_with_esp_idf = only_with_framework(Framework.ESP_IDF)
# Adapted from: # Adapted from:
@ -1966,26 +2018,6 @@ def source_refresh(value: str):
return positive_time_period_seconds(value) return positive_time_period_seconds(value)
@dataclass(frozen=True, order=True)
class Version:
major: int
minor: int
patch: int
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
@classmethod
def parse(cls, value: str) -> Version:
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
if match is None:
raise ValueError(f"Not a valid version number {value}")
major = int(match[1])
minor = int(match[2])
patch = int(match[3])
return Version(major=major, minor=minor, patch=patch)
def version_number(value): def version_number(value):
value = string_strict(value) value = string_strict(value)
try: try:

View File

@ -71,8 +71,11 @@ void Application::setup() {
do { do {
uint8_t new_app_state = STATUS_LED_WARNING; uint8_t new_app_state = STATUS_LED_WARNING;
this->scheduler.call(millis()); uint32_t now = millis();
this->feed_wdt();
// Process pending loop enables to handle GPIO interrupts during setup
this->before_loop_tasks_(now);
for (uint32_t j = 0; j <= i; j++) { for (uint32_t j = 0; j <= i; j++) {
// Update loop_component_start_time_ right before calling each component // Update loop_component_start_time_ right before calling each component
this->loop_component_start_time_ = millis(); this->loop_component_start_time_ = millis();
@ -81,6 +84,8 @@ void Application::setup() {
this->app_state_ |= new_app_state; this->app_state_ |= new_app_state;
this->feed_wdt(); this->feed_wdt();
} }
this->after_loop_tasks_();
this->app_state_ = new_app_state; this->app_state_ = new_app_state;
yield(); yield();
} while (!component->can_proceed()); } while (!component->can_proceed());
@ -100,27 +105,7 @@ void Application::loop() {
// Get the initial loop time at the start // Get the initial loop time at the start
uint32_t last_op_end_time = millis(); uint32_t last_op_end_time = millis();
this->scheduler.call(last_op_end_time); this->before_loop_tasks_(last_op_end_time);
// Feed WDT with time
this->feed_wdt(last_op_end_time);
// Process any pending enable_loop requests from ISRs
// This must be done before marking in_loop_ = true to avoid race conditions
if (this->has_pending_enable_loop_requests_) {
// Clear flag BEFORE processing to avoid race condition
// If ISR sets it during processing, we'll catch it next loop iteration
// This is safe because:
// 1. Each component has its own pending_enable_loop_ flag that we check
// 2. If we can't process a component (wrong state), enable_pending_loops_()
// will set this flag back to true
// 3. Any new ISR requests during processing will set the flag again
this->has_pending_enable_loop_requests_ = false;
this->enable_pending_loops_();
}
// Mark that we're in the loop for safe reentrant modifications
this->in_loop_ = true;
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_; for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
this->current_loop_index_++) { this->current_loop_index_++) {
@ -141,7 +126,7 @@ void Application::loop() {
this->feed_wdt(last_op_end_time); this->feed_wdt(last_op_end_time);
} }
this->in_loop_ = false; this->after_loop_tasks_();
this->app_state_ = new_app_state; this->app_state_ = new_app_state;
#ifdef USE_RUNTIME_STATS #ifdef USE_RUNTIME_STATS
@ -411,6 +396,36 @@ void Application::enable_pending_loops_() {
} }
} }
void Application::before_loop_tasks_(uint32_t loop_start_time) {
// Process scheduled tasks
this->scheduler.call(loop_start_time);
// Feed the watchdog timer
this->feed_wdt(loop_start_time);
// Process any pending enable_loop requests from ISRs
// This must be done before marking in_loop_ = true to avoid race conditions
if (this->has_pending_enable_loop_requests_) {
// Clear flag BEFORE processing to avoid race condition
// If ISR sets it during processing, we'll catch it next loop iteration
// This is safe because:
// 1. Each component has its own pending_enable_loop_ flag that we check
// 2. If we can't process a component (wrong state), enable_pending_loops_()
// will set this flag back to true
// 3. Any new ISR requests during processing will set the flag again
this->has_pending_enable_loop_requests_ = false;
this->enable_pending_loops_();
}
// Mark that we're in the loop for safe reentrant modifications
this->in_loop_ = true;
}
void Application::after_loop_tasks_() {
// Clear the in_loop_ flag to indicate we're done processing components
this->in_loop_ = false;
}
#ifdef USE_SOCKET_SELECT_SUPPORT #ifdef USE_SOCKET_SELECT_SUPPORT
bool Application::register_socket_fd(int fd) { bool Application::register_socket_fd(int fd) {
// WARNING: This function is NOT thread-safe and must only be called from the main loop // WARNING: This function is NOT thread-safe and must only be called from the main loop

View File

@ -512,6 +512,8 @@ class Application {
void enable_component_loop_(Component *component); void enable_component_loop_(Component *component);
void enable_pending_loops_(); void enable_pending_loops_();
void activate_looping_component_(uint16_t index); void activate_looping_component_(uint16_t index);
void before_loop_tasks_(uint32_t loop_start_time);
void after_loop_tasks_();
void feed_wdt_arch_(); void feed_wdt_arch_();

View File

@ -54,6 +54,14 @@ class EntityBase {
// Get/set this entity's icon // Get/set this entity's icon
std::string get_icon() const; std::string get_icon() const;
void set_icon(const char *icon); void set_icon(const char *icon);
StringRef get_icon_ref() const {
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
#ifdef USE_ENTITY_ICON
return this->icon_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->icon_c_str_);
#else
return EMPTY_STRING;
#endif
}
#ifdef USE_DEVICES #ifdef USE_DEVICES
// Get/set this entity's device id // Get/set this entity's device id
@ -105,6 +113,11 @@ class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming)
std::string get_device_class(); std::string get_device_class();
/// Manually set the device class. /// Manually set the device class.
void set_device_class(const char *device_class); void set_device_class(const char *device_class);
/// Get the device class as StringRef
StringRef get_device_class_ref() const {
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
return this->device_class_ == nullptr ? EMPTY_STRING : StringRef(this->device_class_);
}
protected: protected:
const char *device_class_{nullptr}; ///< Device class override const char *device_class_{nullptr}; ///< Device class override
@ -116,6 +129,11 @@ class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming)
std::string get_unit_of_measurement(); std::string get_unit_of_measurement();
/// Manually set the unit of measurement. /// Manually set the unit of measurement.
void set_unit_of_measurement(const char *unit_of_measurement); void set_unit_of_measurement(const char *unit_of_measurement);
/// Get the unit of measurement as StringRef
StringRef get_unit_of_measurement_ref() const {
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
return this->unit_of_measurement_ == nullptr ? EMPTY_STRING : StringRef(this->unit_of_measurement_);
}
protected: protected:
const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override

View File

@ -343,6 +343,10 @@ def create_field_type_info(
if field.type == 12: if field.type == 12:
return BytesType(field, needs_decode, needs_encode) return BytesType(field, needs_decode, needs_encode)
# Special handling for string fields
if field.type == 9:
return StringType(field, needs_decode, needs_encode)
validate_field_type(field.type, field.name) validate_field_type(field.type, field.name)
return TYPE_INFO[field.type](field) return TYPE_INFO[field.type](field)
@ -543,12 +547,67 @@ class StringType(TypeInfo):
encode_func = "encode_string" encode_func = "encode_string"
wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2 wire_type = WireType.LENGTH_DELIMITED # Uses wire type 2
@property
def public_content(self) -> list[str]:
content: list[str] = []
# Add std::string storage if message needs decoding
if self._needs_decode:
content.append(f"std::string {self.field_name}{{}};")
if self._needs_encode:
content.extend(
[
# Add StringRef field if message needs encoding
f"StringRef {self.field_name}_ref_{{}};",
# Add setter method if message needs encoding
f"void set_{self.field_name}(const StringRef &ref) {{",
f" this->{self.field_name}_ref_ = ref;",
"}",
]
)
return content
@property
def encode_content(self) -> str:
return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);"
def dump(self, name): def dump(self, name):
o = f'out.append("\'").append({name}).append("\'");' # If name is 'it', this is a repeated field element - always use string
return o if name == "it":
return "append_quoted_string(out, StringRef(it));"
# For SOURCE_CLIENT only, always use std::string
if not self._needs_encode:
return f'out.append("\'").append(this->{self.field_name}).append("\'");'
# For SOURCE_SERVER, always use StringRef
if not self._needs_decode:
return f"append_quoted_string(out, this->{self.field_name}_ref_);"
# For SOURCE_BOTH, check if StringRef is set (sending) or use string (received)
return (
f"if (!this->{self.field_name}_ref_.empty()) {{"
f' out.append("\'").append(this->{self.field_name}_ref_.c_str()).append("\'");'
f"}} else {{"
f' out.append("\'").append(this->{self.field_name}).append("\'");'
f"}}"
)
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_string_field") # For SOURCE_CLIENT only messages, use the string field directly
if not self._needs_encode:
return self._get_simple_size_calculation(name, force, "add_string_field")
# Check if this is being called from a repeated field context
# In that case, 'name' will be 'it' and we need to use the repeated version
if name == "it":
# For repeated fields, we need to use add_string_field_repeated which includes field ID
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field_repeated(total_size, {field_id_size}, it);"
# For messages that need encoding, use the StringRef size
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}_ref_.size());"
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
@ -1905,6 +1964,7 @@ def main() -> None:
#pragma once #pragma once
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/string_ref.h"
#include "proto.h" #include "proto.h"
@ -1938,6 +1998,15 @@ namespace api {
namespace esphome { namespace esphome {
namespace api { namespace api {
// Helper function to append a quoted string, handling empty StringRef
static inline void append_quoted_string(std::string &out, const StringRef &ref) {
out.append("'");
if (!ref.empty()) {
out.append(ref.c_str());
}
out.append("'");
}
""" """
content += "namespace enums {\n\n" content += "namespace enums {\n\n"
@ -2177,7 +2246,13 @@ static const char *const TAG = "api.service";
cpp += f"#ifdef {ifdef}\n" cpp += f"#ifdef {ifdef}\n"
hpp_protected += f" void {on_func}(const {inp} &msg) override;\n" hpp_protected += f" void {on_func}(const {inp} &msg) override;\n"
hpp += f" virtual {ret} {func}(const {inp} &msg) = 0;\n"
# For non-void methods, generate a send_ method instead of return-by-value
if is_void:
hpp += f" virtual void {func}(const {inp} &msg) = 0;\n"
else:
hpp += f" virtual bool send_{func}_response(const {inp} &msg) = 0;\n"
cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n" cpp += f"void {class_name}::{on_func}(const {inp} &msg) {{\n"
# Start with authentication/connection check if needed # Start with authentication/connection check if needed
@ -2195,10 +2270,7 @@ static const char *const TAG = "api.service";
if is_void: if is_void:
handler_body = f"this->{func}(msg);\n" handler_body = f"this->{func}(msg);\n"
else: else:
handler_body = f"{ret} ret = this->{func}(msg);\n" handler_body = f"if (!this->send_{func}_response(msg)) {{\n"
handler_body += (
f"if (!this->send_message(ret, {ret}::MESSAGE_TYPE)) {{\n"
)
handler_body += " this->on_fatal_error();\n" handler_body += " this->on_fatal_error();\n"
handler_body += "}\n" handler_body += "}\n"
@ -2210,8 +2282,7 @@ static const char *const TAG = "api.service";
if is_void: if is_void:
body += f"this->{func}(msg);\n" body += f"this->{func}(msg);\n"
else: else:
body += f"{ret} ret = this->{func}(msg);\n" body += f"if (!this->send_{func}_response(msg)) {{\n"
body += f"if (!this->send_message(ret, {ret}::MESSAGE_TYPE)) {{\n"
body += " this->on_fatal_error();\n" body += " this->on_fatal_error();\n"
body += "}\n" body += "}\n"

View File

@ -266,7 +266,7 @@ def test_framework_specific_errors(
with pytest.raises( with pytest.raises(
cv.Invalid, cv.Invalid,
match=r"This feature is only available with frameworks \['esp-idf'\]", match=r"This feature is only available with framework\(s\) esp-idf",
): ):
run_schema_validation({"model": "wt32-sc01-plus"}) run_schema_validation({"model": "wt32-sc01-plus"})