diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index e28b244722..398a56217f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -27,8 +27,38 @@ namespace api { static const char *const TAG = "api.connection"; static const int ESP32_CAMERA_STOP_STREAM = 5000; +// helper for allowing only unique entries in the queue +void DeferredMessageQueue::dmq_push_back_with_dedup_(void *source, send_message_t *send_message) { + DeferredMessage item(source, send_message); + + auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(), + [&item](const DeferredMessage &test) -> bool { return test == item; }); + + if (iter != this->deferred_queue_.end()) { + (*iter) = item; + } else { + this->deferred_queue_.push_back(item); + } +} + +void DeferredMessageQueue::process_queue() { + while (!deferred_queue_.empty()) { + DeferredMessage &de = deferred_queue_.front(); + if (de.send_message_(this->api_connection_, de.source_)) { + // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen + deferred_queue_.erase(deferred_queue_.begin()); + } else { + break; + } + } +} + +void DeferredMessageQueue::defer(void *source, send_message_t *send_message) { + this->dmq_push_back_with_dedup_(source, send_message); +} + APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) - : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { + : parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) { this->proto_write_buffer_.reserve(64); #if defined(USE_API_PLAINTEXT) @@ -115,8 +145,12 @@ void APIConnection::loop() { return; } - this->list_entities_iterator_.advance(); - this->initial_state_iterator_.advance(); + this->deferred_message_queue_.process_queue(); + + if (!this->list_entities_iterator_.completed()) + this->list_entities_iterator_.advance(); + if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed()) + this->initial_state_iterator_.advance(); static uint32_t keepalive = 60000; static uint8_t max_ping_retries = 60; @@ -209,13 +243,29 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary if (!this->state_subscription_) return false; + if (!APIConnection::try_send_binary_sensor_state(this, binary_sensor, state)) { + this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_state); + } +} +void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { + if (!APIConnection::try_send_binary_sensor_info(this, binary_sensor)) { + this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_info); + } +} +bool APIConnection::try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor) { + binary_sensor::BinarySensor *binary_sensor = reinterpret_cast(v_binary_sensor); + return APIConnection::try_send_binary_sensor_state(api, binary_sensor, binary_sensor->state); +} +bool APIConnection::try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, + bool state) { BinarySensorStateResponse resp; resp.key = binary_sensor->get_object_id_hash(); resp.state = state; resp.missing_state = !binary_sensor->has_state(); - return this->send_binary_sensor_state_response(resp); + return api->send_binary_sensor_state_response(resp); } -bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { +bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor) { + binary_sensor::BinarySensor *binary_sensor = reinterpret_cast(v_binary_sensor); ListEntitiesBinarySensorResponse msg; msg.object_id = binary_sensor->get_object_id(); msg.key = binary_sensor->get_object_id_hash(); @@ -227,7 +277,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); msg.entity_category = static_cast(binary_sensor->get_entity_category()); - return this->send_list_entities_binary_sensor_response(msg); + return api->send_list_entities_binary_sensor_response(msg); } #endif @@ -236,6 +286,17 @@ bool APIConnection::send_cover_state(cover::Cover *cover) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_cover_state(this, cover)) { + this->deferred_message_queue_.defer(cover, try_send_cover_state); + } +} +void APIConnection::send_cover_info(cover::Cover *cover) { + if (!APIConnection::try_send_cover_info(this, cover)) { + this->deferred_message_queue_.defer(cover, try_send_cover_info); + } +} +bool APIConnection::try_send_cover_state(APIConnection *api, void *v_cover) { + cover::Cover *cover = reinterpret_cast(v_cover); auto traits = cover->get_traits(); CoverStateResponse resp{}; resp.key = cover->get_object_id_hash(); @@ -245,9 +306,10 @@ bool APIConnection::send_cover_state(cover::Cover *cover) { if (traits.get_supports_tilt()) resp.tilt = cover->tilt; resp.current_operation = static_cast(cover->current_operation); - return this->send_cover_state_response(resp); + return api->send_cover_state_response(resp); } -bool APIConnection::send_cover_info(cover::Cover *cover) { +bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) { + cover::Cover *cover = reinterpret_cast(v_cover); auto traits = cover->get_traits(); ListEntitiesCoverResponse msg; msg.key = cover->get_object_id_hash(); @@ -263,7 +325,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); msg.entity_category = static_cast(cover->get_entity_category()); - return this->send_list_entities_cover_response(msg); + return api->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { cover::Cover *cover = App.get_cover_by_key(msg.key); @@ -299,6 +361,17 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_fan_state(this, fan)) { + this->deferred_message_queue_.defer(fan, try_send_fan_state); + } +} +void APIConnection::send_fan_info(fan::Fan *fan) { + if (!APIConnection::try_send_fan_info(this, fan)) { + this->deferred_message_queue_.defer(fan, try_send_fan_info); + } +} +bool APIConnection::try_send_fan_state(APIConnection *api, void *v_fan) { + fan::Fan *fan = reinterpret_cast(v_fan); auto traits = fan->get_traits(); FanStateResponse resp{}; resp.key = fan->get_object_id_hash(); @@ -312,9 +385,10 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { resp.direction = static_cast(fan->direction); if (traits.supports_preset_modes()) resp.preset_mode = fan->preset_mode; - return this->send_fan_state_response(resp); + return api->send_fan_state_response(resp); } -bool APIConnection::send_fan_info(fan::Fan *fan) { +bool APIConnection::try_send_fan_info(APIConnection *api, void *v_fan) { + fan::Fan *fan = reinterpret_cast(v_fan); auto traits = fan->get_traits(); ListEntitiesFanResponse msg; msg.key = fan->get_object_id_hash(); @@ -331,7 +405,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) { msg.disabled_by_default = fan->is_disabled_by_default(); msg.icon = fan->get_icon(); msg.entity_category = static_cast(fan->get_entity_category()); - return this->send_list_entities_fan_response(msg); + return api->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { fan::Fan *fan = App.get_fan_by_key(msg.key); @@ -360,6 +434,17 @@ bool APIConnection::send_light_state(light::LightState *light) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_light_state(this, light)) { + this->deferred_message_queue_.defer(light, try_send_light_state); + } +} +void APIConnection::send_light_info(light::LightState *light) { + if (!APIConnection::try_send_light_info(this, light)) { + this->deferred_message_queue_.defer(light, try_send_light_info); + } +} +bool APIConnection::try_send_light_state(APIConnection *api, void *v_light) { + light::LightState *light = reinterpret_cast(v_light); auto traits = light->get_traits(); auto values = light->remote_values; auto color_mode = values.get_color_mode(); @@ -379,9 +464,10 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); - return this->send_light_state_response(resp); + return api->send_light_state_response(resp); } -bool APIConnection::send_light_info(light::LightState *light) { +bool APIConnection::try_send_light_info(APIConnection *api, void *v_light) { + light::LightState *light = reinterpret_cast(v_light); auto traits = light->get_traits(); ListEntitiesLightResponse msg; msg.key = light->get_object_id_hash(); @@ -414,7 +500,7 @@ bool APIConnection::send_light_info(light::LightState *light) { for (auto *effect : light->get_effects()) msg.effects.push_back(effect->get_name()); } - return this->send_list_entities_light_response(msg); + return api->send_list_entities_light_response(msg); } void APIConnection::light_command(const LightCommandRequest &msg) { light::LightState *light = App.get_light_by_key(msg.key); @@ -458,13 +544,28 @@ bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_sensor_state(this, sensor, state)) { + this->deferred_message_queue_.defer(sensor, try_send_sensor_state); + } +} +void APIConnection::send_sensor_info(sensor::Sensor *sensor) { + if (!APIConnection::try_send_sensor_info(this, sensor)) { + this->deferred_message_queue_.defer(sensor, try_send_sensor_info); + } +} +bool APIConnection::try_send_sensor_state(APIConnection *api, void *v_sensor) { + sensor::Sensor *sensor = reinterpret_cast(v_sensor); + return APIConnection::try_send_sensor_state(api, sensor, sensor->state); +} +bool APIConnection::try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state) { SensorStateResponse resp{}; resp.key = sensor->get_object_id_hash(); resp.state = state; resp.missing_state = !sensor->has_state(); - return this->send_sensor_state_response(resp); + return api->send_sensor_state_response(resp); } -bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { +bool APIConnection::try_send_sensor_info(APIConnection *api, void *v_sensor) { + sensor::Sensor *sensor = reinterpret_cast(v_sensor); ListEntitiesSensorResponse msg; msg.key = sensor->get_object_id_hash(); msg.object_id = sensor->get_object_id(); @@ -481,7 +582,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); msg.entity_category = static_cast(sensor->get_entity_category()); - return this->send_list_entities_sensor_response(msg); + return api->send_list_entities_sensor_response(msg); } #endif @@ -490,12 +591,27 @@ bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_switch_state(this, a_switch, state)) { + this->deferred_message_queue_.defer(a_switch, try_send_switch_state); + } +} +void APIConnection::send_switch_info(switch_::Switch *a_switch) { + if (!APIConnection::try_send_switch_info(this, a_switch)) { + this->deferred_message_queue_.defer(a_switch, try_send_switch_info); + } +} +bool APIConnection::try_send_switch_state(APIConnection *api, void *v_a_switch) { + switch_::Switch *a_switch = reinterpret_cast(v_a_switch); + return APIConnection::try_send_switch_state(api, a_switch, a_switch->state); +} +bool APIConnection::try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state) { SwitchStateResponse resp{}; resp.key = a_switch->get_object_id_hash(); resp.state = state; - return this->send_switch_state_response(resp); + return api->send_switch_state_response(resp); } -bool APIConnection::send_switch_info(switch_::Switch *a_switch) { +bool APIConnection::try_send_switch_info(APIConnection *api, void *v_a_switch) { + switch_::Switch *a_switch = reinterpret_cast(v_a_switch); ListEntitiesSwitchResponse msg; msg.key = a_switch->get_object_id_hash(); msg.object_id = a_switch->get_object_id(); @@ -507,7 +623,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.disabled_by_default = a_switch->is_disabled_by_default(); msg.entity_category = static_cast(a_switch->get_entity_category()); msg.device_class = a_switch->get_device_class(); - return this->send_list_entities_switch_response(msg); + return api->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { switch_::Switch *a_switch = App.get_switch_by_key(msg.key); @@ -527,13 +643,29 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, if (!this->state_subscription_) return false; + if (!APIConnection::try_send_text_sensor_state(this, text_sensor, state)) { + this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_state); + } +} +void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { + if (!APIConnection::try_send_text_sensor_info(this, text_sensor)) { + this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_info); + } +} +bool APIConnection::try_send_text_sensor_state(APIConnection *api, void *v_text_sensor) { + text_sensor::TextSensor *text_sensor = reinterpret_cast(v_text_sensor); + return APIConnection::try_send_text_sensor_state(api, text_sensor, text_sensor->state); +} +bool APIConnection::try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, + std::string state) { TextSensorStateResponse resp{}; resp.key = text_sensor->get_object_id_hash(); resp.state = std::move(state); resp.missing_state = !text_sensor->has_state(); - return this->send_text_sensor_state_response(resp); + return api->send_text_sensor_state_response(resp); } -bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { +bool APIConnection::try_send_text_sensor_info(APIConnection *api, void *v_text_sensor) { + text_sensor::TextSensor *text_sensor = reinterpret_cast(v_text_sensor); ListEntitiesTextSensorResponse msg; msg.key = text_sensor->get_object_id_hash(); msg.object_id = text_sensor->get_object_id(); @@ -545,7 +677,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) msg.disabled_by_default = text_sensor->is_disabled_by_default(); msg.entity_category = static_cast(text_sensor->get_entity_category()); msg.device_class = text_sensor->get_device_class(); - return this->send_list_entities_text_sensor_response(msg); + return api->send_list_entities_text_sensor_response(msg); } #endif @@ -554,6 +686,17 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_climate_state(this, climate)) { + this->deferred_message_queue_.defer(climate, try_send_climate_state); + } +} +void APIConnection::send_climate_info(climate::Climate *climate) { + if (!APIConnection::try_send_climate_info(this, climate)) { + this->deferred_message_queue_.defer(climate, try_send_climate_info); + } +} +bool APIConnection::try_send_climate_state(APIConnection *api, void *v_climate) { + climate::Climate *climate = reinterpret_cast(v_climate); auto traits = climate->get_traits(); ClimateStateResponse resp{}; resp.key = climate->get_object_id_hash(); @@ -582,9 +725,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { resp.current_humidity = climate->current_humidity; if (traits.get_supports_target_humidity()) resp.target_humidity = climate->target_humidity; - return this->send_climate_state_response(resp); + return api->send_climate_state_response(resp); } -bool APIConnection::send_climate_info(climate::Climate *climate) { +bool APIConnection::try_send_climate_info(APIConnection *api, void *v_climate) { + climate::Climate *climate = reinterpret_cast(v_climate); auto traits = climate->get_traits(); ListEntitiesClimateResponse msg; msg.key = climate->get_object_id_hash(); @@ -625,7 +769,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.supported_custom_presets.push_back(custom_preset); for (auto swing_mode : traits.get_supported_swing_modes()) msg.supported_swing_modes.push_back(static_cast(swing_mode)); - return this->send_list_entities_climate_response(msg); + return api->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { climate::Climate *climate = App.get_climate_by_key(msg.key); @@ -662,13 +806,28 @@ bool APIConnection::send_number_state(number::Number *number, float state) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_number_state(this, number, state)) { + this->deferred_message_queue_.defer(number, try_send_number_state); + } +} +void APIConnection::send_number_info(number::Number *number) { + if (!APIConnection::try_send_number_info(this, number)) { + this->deferred_message_queue_.defer(number, try_send_number_info); + } +} +bool APIConnection::try_send_number_state(APIConnection *api, void *v_number) { + number::Number *number = reinterpret_cast(v_number); + return APIConnection::try_send_number_state(api, number, number->state); +} +bool APIConnection::try_send_number_state(APIConnection *api, number::Number *number, float state) { NumberStateResponse resp{}; resp.key = number->get_object_id_hash(); resp.state = state; resp.missing_state = !number->has_state(); - return this->send_number_state_response(resp); + return api->send_number_state_response(resp); } -bool APIConnection::send_number_info(number::Number *number) { +bool APIConnection::try_send_number_info(APIConnection *api, void *v_number) { + number::Number *number = reinterpret_cast(v_number); ListEntitiesNumberResponse msg; msg.key = number->get_object_id_hash(); msg.object_id = number->get_object_id(); @@ -686,7 +845,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); - return this->send_list_entities_number_response(msg); + return api->send_list_entities_number_response(msg); } void APIConnection::number_command(const NumberCommandRequest &msg) { number::Number *number = App.get_number_by_key(msg.key); @@ -704,15 +863,27 @@ bool APIConnection::send_date_state(datetime::DateEntity *date) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_date_state(this, date)) { + this->deferred_message_queue_.defer(date, try_send_date_state); + } +} +void APIConnection::send_date_info(datetime::DateEntity *date) { + if (!APIConnection::try_send_date_info(this, date)) { + this->deferred_message_queue_.defer(date, try_send_date_info); + } +} +bool APIConnection::try_send_date_state(APIConnection *api, void *v_date) { + datetime::DateEntity *date = reinterpret_cast(v_date); DateStateResponse resp{}; resp.key = date->get_object_id_hash(); resp.missing_state = !date->has_state(); resp.year = date->year; resp.month = date->month; resp.day = date->day; - return this->send_date_state_response(resp); + return api->send_date_state_response(resp); } -bool APIConnection::send_date_info(datetime::DateEntity *date) { +bool APIConnection::try_send_date_info(APIConnection *api, void *v_date) { + datetime::DateEntity *date = reinterpret_cast(v_date); ListEntitiesDateResponse msg; msg.key = date->get_object_id_hash(); msg.object_id = date->get_object_id(); @@ -723,7 +894,7 @@ bool APIConnection::send_date_info(datetime::DateEntity *date) { msg.disabled_by_default = date->is_disabled_by_default(); msg.entity_category = static_cast(date->get_entity_category()); - return this->send_list_entities_date_response(msg); + return api->send_list_entities_date_response(msg); } void APIConnection::date_command(const DateCommandRequest &msg) { datetime::DateEntity *date = App.get_date_by_key(msg.key); @@ -741,15 +912,27 @@ bool APIConnection::send_time_state(datetime::TimeEntity *time) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_time_state(this, time)) { + this->deferred_message_queue_.defer(time, try_send_time_state); + } +} +void APIConnection::send_time_info(datetime::TimeEntity *time) { + if (!APIConnection::try_send_time_info(this, time)) { + this->deferred_message_queue_.defer(time, try_send_time_info); + } +} +bool APIConnection::try_send_time_state(APIConnection *api, void *v_time) { + datetime::TimeEntity *time = reinterpret_cast(v_time); TimeStateResponse resp{}; resp.key = time->get_object_id_hash(); resp.missing_state = !time->has_state(); resp.hour = time->hour; resp.minute = time->minute; resp.second = time->second; - return this->send_time_state_response(resp); + return api->send_time_state_response(resp); } -bool APIConnection::send_time_info(datetime::TimeEntity *time) { +bool APIConnection::try_send_time_info(APIConnection *api, void *v_time) { + datetime::TimeEntity *time = reinterpret_cast(v_time); ListEntitiesTimeResponse msg; msg.key = time->get_object_id_hash(); msg.object_id = time->get_object_id(); @@ -760,7 +943,7 @@ bool APIConnection::send_time_info(datetime::TimeEntity *time) { msg.disabled_by_default = time->is_disabled_by_default(); msg.entity_category = static_cast(time->get_entity_category()); - return this->send_list_entities_time_response(msg); + return api->send_list_entities_time_response(msg); } void APIConnection::time_command(const TimeCommandRequest &msg) { datetime::TimeEntity *time = App.get_time_by_key(msg.key); @@ -778,6 +961,17 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_datetime_state(this, datetime)) { + this->deferred_message_queue_.defer(datetime, try_send_datetime_state); + } +} +void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { + if (!APIConnection::try_send_datetime_info(this, datetime)) { + this->deferred_message_queue_.defer(datetime, try_send_datetime_info); + } +} +bool APIConnection::try_send_datetime_state(APIConnection *api, void *v_datetime) { + datetime::DateTimeEntity *datetime = reinterpret_cast(v_datetime); DateTimeStateResponse resp{}; resp.key = datetime->get_object_id_hash(); resp.missing_state = !datetime->has_state(); @@ -785,9 +979,10 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { ESPTime state = datetime->state_as_esptime(); resp.epoch_seconds = state.timestamp; } - return this->send_date_time_state_response(resp); + return api->send_date_time_state_response(resp); } -bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { +bool APIConnection::try_send_datetime_info(APIConnection *api, void *v_datetime) { + datetime::DateTimeEntity *datetime = reinterpret_cast(v_datetime); ListEntitiesDateTimeResponse msg; msg.key = datetime->get_object_id_hash(); msg.object_id = datetime->get_object_id(); @@ -798,7 +993,7 @@ bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { msg.disabled_by_default = datetime->is_disabled_by_default(); msg.entity_category = static_cast(datetime->get_entity_category()); - return this->send_list_entities_date_time_response(msg); + return api->send_list_entities_date_time_response(msg); } void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key); @@ -816,13 +1011,28 @@ bool APIConnection::send_text_state(text::Text *text, std::string state) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_text_state(this, text, state)) { + this->deferred_message_queue_.defer(text, try_send_text_state); + } +} +void APIConnection::send_text_info(text::Text *text) { + if (!APIConnection::try_send_text_info(this, text)) { + this->deferred_message_queue_.defer(text, try_send_text_info); + } +} +bool APIConnection::try_send_text_state(APIConnection *api, void *v_text) { + text::Text *text = reinterpret_cast(v_text); + return APIConnection::try_send_text_state(api, text, text->state); +} +bool APIConnection::try_send_text_state(APIConnection *api, text::Text *text, std::string state) { TextStateResponse resp{}; resp.key = text->get_object_id_hash(); resp.state = std::move(state); resp.missing_state = !text->has_state(); - return this->send_text_state_response(resp); + return api->send_text_state_response(resp); } -bool APIConnection::send_text_info(text::Text *text) { +bool APIConnection::try_send_text_info(APIConnection *api, void *v_text) { + text::Text *text = reinterpret_cast(v_text); ListEntitiesTextResponse msg; msg.key = text->get_object_id_hash(); msg.object_id = text->get_object_id(); @@ -836,7 +1046,7 @@ bool APIConnection::send_text_info(text::Text *text) { msg.max_length = text->traits.get_max_length(); msg.pattern = text->traits.get_pattern(); - return this->send_list_entities_text_response(msg); + return api->send_list_entities_text_response(msg); } void APIConnection::text_command(const TextCommandRequest &msg) { text::Text *text = App.get_text_by_key(msg.key); @@ -854,13 +1064,28 @@ bool APIConnection::send_select_state(select::Select *select, std::string state) if (!this->state_subscription_) return false; + if (!APIConnection::try_send_select_state(this, select, state)) { + this->deferred_message_queue_.defer(select, try_send_select_state); + } +} +void APIConnection::send_select_info(select::Select *select) { + if (!APIConnection::try_send_select_info(this, select)) { + this->deferred_message_queue_.defer(select, try_send_select_info); + } +} +bool APIConnection::try_send_select_state(APIConnection *api, void *v_select) { + select::Select *select = reinterpret_cast(v_select); + return APIConnection::try_send_select_state(api, select, select->state); +} +bool APIConnection::try_send_select_state(APIConnection *api, select::Select *select, std::string state) { SelectStateResponse resp{}; resp.key = select->get_object_id_hash(); resp.state = std::move(state); resp.missing_state = !select->has_state(); - return this->send_select_state_response(resp); + return api->send_select_state_response(resp); } -bool APIConnection::send_select_info(select::Select *select) { +bool APIConnection::try_send_select_info(APIConnection *api, void *v_select) { + select::Select *select = reinterpret_cast(v_select); ListEntitiesSelectResponse msg; msg.key = select->get_object_id_hash(); msg.object_id = select->get_object_id(); @@ -874,7 +1099,7 @@ bool APIConnection::send_select_info(select::Select *select) { for (const auto &option : select->traits.get_options()) msg.options.push_back(option); - return this->send_list_entities_select_response(msg); + return api->send_list_entities_select_response(msg); } void APIConnection::select_command(const SelectCommandRequest &msg) { select::Select *select = App.get_select_by_key(msg.key); @@ -888,7 +1113,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { #endif #ifdef USE_BUTTON -bool APIConnection::send_button_info(button::Button *button) { +void APIConnection::send_button_info(button::Button *button) { + if (!APIConnection::try_send_button_info(this, button)) { + this->deferred_message_queue_.defer(button, try_send_button_info); + } +} +bool APIConnection::try_send_button_info(APIConnection *api, void *v_button) { + button::Button *button = reinterpret_cast(v_button); ListEntitiesButtonResponse msg; msg.key = button->get_object_id_hash(); msg.object_id = button->get_object_id(); @@ -899,7 +1130,7 @@ bool APIConnection::send_button_info(button::Button *button) { msg.disabled_by_default = button->is_disabled_by_default(); msg.entity_category = static_cast(button->get_entity_category()); msg.device_class = button->get_device_class(); - return this->send_list_entities_button_response(msg); + return api->send_list_entities_button_response(msg); } void APIConnection::button_command(const ButtonCommandRequest &msg) { button::Button *button = App.get_button_by_key(msg.key); @@ -915,12 +1146,27 @@ bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_lock_state(this, a_lock, state)) { + this->deferred_message_queue_.defer(a_lock, try_send_lock_state); + } +} +void APIConnection::send_lock_info(lock::Lock *a_lock) { + if (!APIConnection::try_send_lock_info(this, a_lock)) { + this->deferred_message_queue_.defer(a_lock, try_send_lock_info); + } +} +bool APIConnection::try_send_lock_state(APIConnection *api, void *v_a_lock) { + lock::Lock *a_lock = reinterpret_cast(v_a_lock); + return APIConnection::try_send_lock_state(api, a_lock, a_lock->state); +} +bool APIConnection::try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state) { LockStateResponse resp{}; resp.key = a_lock->get_object_id_hash(); resp.state = static_cast(state); - return this->send_lock_state_response(resp); + return api->send_lock_state_response(resp); } -bool APIConnection::send_lock_info(lock::Lock *a_lock) { +bool APIConnection::try_send_lock_info(APIConnection *api, void *v_a_lock) { + lock::Lock *a_lock = reinterpret_cast(v_a_lock); ListEntitiesLockResponse msg; msg.key = a_lock->get_object_id_hash(); msg.object_id = a_lock->get_object_id(); @@ -933,7 +1179,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) { msg.entity_category = static_cast(a_lock->get_entity_category()); msg.supports_open = a_lock->traits.get_supports_open(); msg.requires_code = a_lock->traits.get_requires_code(); - return this->send_list_entities_lock_response(msg); + return api->send_list_entities_lock_response(msg); } void APIConnection::lock_command(const LockCommandRequest &msg) { lock::Lock *a_lock = App.get_lock_by_key(msg.key); @@ -959,13 +1205,25 @@ bool APIConnection::send_valve_state(valve::Valve *valve) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_valve_state(this, valve)) { + this->deferred_message_queue_.defer(valve, try_send_valve_state); + } +} +void APIConnection::send_valve_info(valve::Valve *valve) { + if (!APIConnection::try_send_valve_info(this, valve)) { + this->deferred_message_queue_.defer(valve, try_send_valve_info); + } +} +bool APIConnection::try_send_valve_state(APIConnection *api, void *v_valve) { + valve::Valve *valve = reinterpret_cast(v_valve); ValveStateResponse resp{}; resp.key = valve->get_object_id_hash(); resp.position = valve->position; resp.current_operation = static_cast(valve->current_operation); - return this->send_valve_state_response(resp); + return api->send_valve_state_response(resp); } -bool APIConnection::send_valve_info(valve::Valve *valve) { +bool APIConnection::try_send_valve_info(APIConnection *api, void *v_valve) { + valve::Valve *valve = reinterpret_cast(v_valve); auto traits = valve->get_traits(); ListEntitiesValveResponse msg; msg.key = valve->get_object_id_hash(); @@ -980,7 +1238,7 @@ bool APIConnection::send_valve_info(valve::Valve *valve) { msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); - return this->send_list_entities_valve_response(msg); + return api->send_list_entities_valve_response(msg); } void APIConnection::valve_command(const ValveCommandRequest &msg) { valve::Valve *valve = App.get_valve_by_key(msg.key); @@ -1001,6 +1259,17 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla if (!this->state_subscription_) return false; + if (!APIConnection::try_send_media_player_state(this, media_player)) { + this->deferred_message_queue_.defer(media_player, try_send_media_player_state); + } +} +void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { + if (!APIConnection::try_send_media_player_info(this, media_player)) { + this->deferred_message_queue_.defer(media_player, try_send_media_player_info); + } +} +bool APIConnection::try_send_media_player_state(APIConnection *api, void *v_media_player) { + media_player::MediaPlayer *media_player = reinterpret_cast(v_media_player); MediaPlayerStateResponse resp{}; resp.key = media_player->get_object_id_hash(); @@ -1010,9 +1279,10 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla resp.state = static_cast(report_state); resp.volume = media_player->volume; resp.muted = media_player->is_muted(); - return this->send_media_player_state_response(resp); + return api->send_media_player_state_response(resp); } -bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { +bool APIConnection::try_send_media_player_info(APIConnection *api, void *v_media_player) { + media_player::MediaPlayer *media_player = reinterpret_cast(v_media_player); ListEntitiesMediaPlayerResponse msg; msg.key = media_player->get_object_id_hash(); msg.object_id = media_player->get_object_id(); @@ -1036,7 +1306,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play msg.supported_formats.push_back(media_format); } - return this->send_list_entities_media_player_response(msg); + return api->send_list_entities_media_player_response(msg); } void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); @@ -1061,7 +1331,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { #endif #ifdef USE_ESP32_CAMERA -void APIConnection::send_camera_state(std::shared_ptr image) { +void APIConnection::set_camera_state(std::shared_ptr image) { if (!this->state_subscription_) return; if (this->image_reader_.available()) @@ -1070,7 +1340,13 @@ void APIConnection::send_camera_state(std::shared_ptr image->was_requested_by(esphome::esp32_camera::IDLE)) this->image_reader_.set_image(std::move(image)); } -bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { +void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { + if (!APIConnection::try_send_camera_info(this, camera)) { + this->deferred_message_queue_.defer(camera, try_send_camera_info); + } +} +bool APIConnection::try_send_camera_info(APIConnection *api, void *v_camera) { + esp32_camera::ESP32Camera *camera = reinterpret_cast(v_camera); ListEntitiesCameraResponse msg; msg.key = camera->get_object_id_hash(); msg.object_id = camera->get_object_id(); @@ -1080,7 +1356,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); msg.entity_category = static_cast(camera->get_entity_category()); - return this->send_list_entities_camera_response(msg); + return api->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { if (esp32_camera::global_esp32_camera == nullptr) @@ -1267,12 +1543,26 @@ bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmCon if (!this->state_subscription_) return false; + if (!APIConnection::try_send_alarm_control_panel_state(this, a_alarm_control_panel)) { + this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_state); + } +} +void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { + if (!APIConnection::try_send_alarm_control_panel_info(this, a_alarm_control_panel)) { + this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_info); + } +} +bool APIConnection::try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel) { + alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = + reinterpret_cast(v_a_alarm_control_panel); AlarmControlPanelStateResponse resp{}; resp.key = a_alarm_control_panel->get_object_id_hash(); resp.state = static_cast(a_alarm_control_panel->get_state()); - return this->send_alarm_control_panel_state_response(resp); + return api->send_alarm_control_panel_state_response(resp); } -bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { +bool APIConnection::try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel) { + alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = + reinterpret_cast(v_a_alarm_control_panel); ListEntitiesAlarmControlPanelResponse msg; msg.key = a_alarm_control_panel->get_object_id_hash(); msg.object_id = a_alarm_control_panel->get_object_id(); @@ -1284,7 +1574,7 @@ bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmCont msg.supported_features = a_alarm_control_panel->get_supported_features(); msg.requires_code = a_alarm_control_panel->get_requires_code(); msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm(); - return this->send_list_entities_alarm_control_panel_response(msg); + return api->send_list_entities_alarm_control_panel_response(msg); } void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key); @@ -1321,13 +1611,28 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe #endif #ifdef USE_EVENT -bool APIConnection::send_event(event::Event *event, std::string event_type) { +void APIConnection::send_event(event::Event *event, std::string event_type) { + if (!APIConnection::try_send_event(this, event, event_type)) { + this->deferred_message_queue_.defer(event, try_send_event); + } +} +void APIConnection::send_event_info(event::Event *event) { + if (!APIConnection::try_send_event_info(this, event)) { + this->deferred_message_queue_.defer(event, try_send_event_info); + } +} +bool APIConnection::try_send_event(APIConnection *api, void *v_event) { + event::Event *event = reinterpret_cast(v_event); + return APIConnection::try_send_event(api, event, *(event->state)); +} +bool APIConnection::try_send_event(APIConnection *api, event::Event *event, std::string event_type) { EventResponse resp{}; resp.key = event->get_object_id_hash(); resp.event_type = std::move(event_type); - return this->send_event_response(resp); + return api->send_event_response(resp); } -bool APIConnection::send_event_info(event::Event *event) { +bool APIConnection::try_send_event_info(APIConnection *api, void *v_event) { + event::Event *event = reinterpret_cast(v_event); ListEntitiesEventResponse msg; msg.key = event->get_object_id_hash(); msg.object_id = event->get_object_id(); @@ -1340,7 +1645,7 @@ bool APIConnection::send_event_info(event::Event *event) { msg.device_class = event->get_device_class(); for (const auto &event_type : event->get_event_types()) msg.event_types.push_back(event_type); - return this->send_list_entities_event_response(msg); + return api->send_list_entities_event_response(msg); } #endif @@ -1349,6 +1654,17 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) { if (!this->state_subscription_) return false; + if (!APIConnection::try_send_update_state(this, update)) { + this->deferred_message_queue_.defer(update, try_send_update_state); + } +} +void APIConnection::send_update_info(update::UpdateEntity *update) { + if (!APIConnection::try_send_update_info(this, update)) { + this->deferred_message_queue_.defer(update, try_send_update_info); + } +} +bool APIConnection::try_send_update_state(APIConnection *api, void *v_update) { + update::UpdateEntity *update = reinterpret_cast(v_update); UpdateStateResponse resp{}; resp.key = update->get_object_id_hash(); resp.missing_state = !update->has_state(); @@ -1365,9 +1681,10 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) { resp.release_url = update->update_info.release_url; } - return this->send_update_state_response(resp); + return api->send_update_state_response(resp); } -bool APIConnection::send_update_info(update::UpdateEntity *update) { +bool APIConnection::try_send_update_info(APIConnection *api, void *v_update) { + update::UpdateEntity *update = reinterpret_cast(v_update); ListEntitiesUpdateResponse msg; msg.key = update->get_object_id_hash(); msg.object_id = update->get_object_id(); @@ -1378,7 +1695,7 @@ bool APIConnection::send_update_info(update::UpdateEntity *update) { msg.disabled_by_default = update->is_disabled_by_default(); msg.entity_category = static_cast(update->get_entity_category()); msg.device_class = update->get_device_class(); - return this->send_list_entities_update_response(msg); + return api->send_list_entities_update_response(msg); } void APIConnection::update_command(const UpdateCommandRequest &msg) { update::UpdateEntity *update = App.get_update_by_key(msg.key); @@ -1402,7 +1719,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { } #endif -bool APIConnection::send_log_message(int level, const char *tag, const char *line) { +bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index f176cf7c56..80d6846868 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -13,6 +13,50 @@ namespace esphome { namespace api { +/* + This class holds a pointer to the event source, the event type, and a pointer to a lambda that will lazily + generate the event body. The source and type allow dedup in the deferred queue and the lambda saves on + having to store the message body upfront. The lambda should not need any closures due to the parameters + so it's only overhead is the pointer to the lambda itself. + That's three pointers, so 12 bytes. The entry in the deferred event queue (a std::vector with no overhead) + is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still 12 bytes total per + entry. +*/ +using send_message_t = bool(APIConnection *, void *); + +class DeferredUpdateEventSourceList; +class DeferredMessageQueue { + struct DeferredMessage { + friend class DeferredMessageQueue; + + protected: + void *source_; + send_message_t *send_message_; + + public: + DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {} + bool operator==(const DeferredMessage &test) const { + return (source_ == test.source_ && send_message_ == test.send_message_); + } + } __attribute__((packed)); + + protected: + // vector is used very specifically for its zero memory overhead even though items are popped from the front (memory + // footprint is more important than speed here) + std::vector deferred_queue_; + APIConnection *api_connection_; + + // helper for allowing only unique entries in the queue + void dmq_push_back_with_dedup_(void *source, send_message_t *send_message); + + public: + DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {} + + void process_queue(); + + void defer(void *source, send_message_t *send_message); +}; + class APIConnection : public APIServerConnection { public: APIConnection(std::unique_ptr socket, APIServer *parent); @@ -27,96 +71,140 @@ class APIConnection : public APIServerConnection { } #ifdef USE_BINARY_SENSOR bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); - bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); + void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); + static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor); + static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state); + static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor); #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - bool send_cover_info(cover::Cover *cover); + void send_cover_info(cover::Cover *cover); + static bool try_send_cover_state(APIConnection *api, void *v_cover); + static bool try_send_cover_info(APIConnection *api, void *v_cover); void cover_command(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - bool send_fan_info(fan::Fan *fan); + void send_fan_info(fan::Fan *fan); + static bool try_send_fan_state(APIConnection *api, void *v_fan); + static bool try_send_fan_info(APIConnection *api, void *v_fan); void fan_command(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - bool send_light_info(light::LightState *light); + void send_light_info(light::LightState *light); + static bool try_send_light_state(APIConnection *api, void *v_light); + static bool try_send_light_info(APIConnection *api, void *v_light); void light_command(const LightCommandRequest &msg) override; #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor, float state); - bool send_sensor_info(sensor::Sensor *sensor); + void send_sensor_info(sensor::Sensor *sensor); + static bool try_send_sensor_state(APIConnection *api, void *v_sensor); + static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state); + static bool try_send_sensor_info(APIConnection *api, void *v_sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch, bool state); - bool send_switch_info(switch_::Switch *a_switch); + void send_switch_info(switch_::Switch *a_switch); + static bool try_send_switch_state(APIConnection *api, void *v_a_switch); + static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state); + static bool try_send_switch_info(APIConnection *api, void *v_a_switch); void switch_command(const SwitchCommandRequest &msg) override; #endif #ifdef USE_TEXT_SENSOR bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); - bool send_text_sensor_info(text_sensor::TextSensor *text_sensor); + void send_text_sensor_info(text_sensor::TextSensor *text_sensor); + static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor); + static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state); + static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor); #endif #ifdef USE_ESP32_CAMERA - void send_camera_state(std::shared_ptr image); - bool send_camera_info(esp32_camera::ESP32Camera *camera); + void set_camera_state(std::shared_ptr image); + void send_camera_info(esp32_camera::ESP32Camera *camera); + static bool try_send_camera_info(APIConnection *api, void *v_camera); void camera_image(const CameraImageRequest &msg) override; #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - bool send_climate_info(climate::Climate *climate); + void send_climate_info(climate::Climate *climate); + static bool try_send_climate_state(APIConnection *api, void *v_climate); + static bool try_send_climate_info(APIConnection *api, void *v_climate); void climate_command(const ClimateCommandRequest &msg) override; #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number, float state); - bool send_number_info(number::Number *number); + void send_number_info(number::Number *number); + static bool try_send_number_state(APIConnection *api, void *v_number); + static bool try_send_number_state(APIConnection *api, number::Number *number, float state); + static bool try_send_number_info(APIConnection *api, void *v_number); void number_command(const NumberCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - bool send_date_info(datetime::DateEntity *date); + void send_date_info(datetime::DateEntity *date); + static bool try_send_date_state(APIConnection *api, void *v_date); + static bool try_send_date_info(APIConnection *api, void *v_date); void date_command(const DateCommandRequest &msg) override; #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - bool send_time_info(datetime::TimeEntity *time); + void send_time_info(datetime::TimeEntity *time); + static bool try_send_time_state(APIConnection *api, void *v_time); + static bool try_send_time_info(APIConnection *api, void *v_time); void time_command(const TimeCommandRequest &msg) override; #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - bool send_datetime_info(datetime::DateTimeEntity *datetime); + void send_datetime_info(datetime::DateTimeEntity *datetime); + static bool try_send_datetime_state(APIConnection *api, void *v_datetime); + static bool try_send_datetime_info(APIConnection *api, void *v_datetime); void datetime_command(const DateTimeCommandRequest &msg) override; #endif #ifdef USE_TEXT bool send_text_state(text::Text *text, std::string state); - bool send_text_info(text::Text *text); + void send_text_info(text::Text *text); + static bool try_send_text_state(APIConnection *api, void *v_text); + static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state); + static bool try_send_text_info(APIConnection *api, void *v_text); void text_command(const TextCommandRequest &msg) override; #endif #ifdef USE_SELECT bool send_select_state(select::Select *select, std::string state); - bool send_select_info(select::Select *select); + void send_select_info(select::Select *select); + static bool try_send_select_state(APIConnection *api, void *v_select); + static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state); + static bool try_send_select_info(APIConnection *api, void *v_select); void select_command(const SelectCommandRequest &msg) override; #endif #ifdef USE_BUTTON - bool send_button_info(button::Button *button); + void send_button_info(button::Button *button); + static bool try_send_button_info(APIConnection *api, void *v_button); void button_command(const ButtonCommandRequest &msg) override; #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock, lock::LockState state); - bool send_lock_info(lock::Lock *a_lock); + void send_lock_info(lock::Lock *a_lock); + static bool try_send_lock_state(APIConnection *api, void *v_a_lock); + static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state); + static bool try_send_lock_info(APIConnection *api, void *v_a_lock); void lock_command(const LockCommandRequest &msg) override; #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - bool send_valve_info(valve::Valve *valve); + void send_valve_info(valve::Valve *valve); + static bool try_send_valve_state(APIConnection *api, void *v_valve); + static bool try_send_valve_info(APIConnection *api, void *v_valve); void valve_command(const ValveCommandRequest &msg) override; #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); - bool send_media_player_info(media_player::MediaPlayer *media_player); + void send_media_player_info(media_player::MediaPlayer *media_player); + static bool try_send_media_player_state(APIConnection *api, void *v_media_player); + static bool try_send_media_player_info(APIConnection *api, void *v_media_player); void media_player_command(const MediaPlayerCommandRequest &msg) override; #endif - bool send_log_message(int level, const char *tag, const char *line); + bool try_send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { if (!this->service_call_subscription_) return; @@ -159,18 +247,25 @@ class APIConnection : public APIServerConnection { #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); - bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); + void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); + static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel); + static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel); void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; #endif #ifdef USE_EVENT - bool send_event(event::Event *event, std::string event_type); - bool send_event_info(event::Event *event); + void send_event(event::Event *event, std::string event_type); + void send_event_info(event::Event *event); + static bool try_send_event(APIConnection *api, void *v_event); + static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type); + static bool try_send_event_info(APIConnection *api, void *v_event); #endif #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - bool send_update_info(update::UpdateEntity *update); + void send_update_info(update::UpdateEntity *update); + static bool try_send_update_state(APIConnection *api, void *v_update); + static bool try_send_update_info(APIConnection *api, void *v_update); void update_command(const UpdateCommandRequest &msg) override; #endif @@ -261,6 +356,7 @@ class APIConnection : public APIServerConnection { bool service_call_subscription_{false}; bool next_close_ = false; APIServer *parent_; + DeferredMessageQueue deferred_message_queue_; InitialStateIterator initial_state_iterator_; ListEntitiesIterator list_entities_iterator_; int state_subs_at_ = -1; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 0fde3e47af..7bbaa51477 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -71,7 +71,7 @@ void APIServer::setup() { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { for (auto &c : this->clients_) { if (!c->remove_) - c->send_log_message(level, tag, message); + c->try_send_log_message(level, tag, message); } }); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 5fa360d170..32791b9c8e 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -9,40 +9,66 @@ namespace api { #ifdef USE_BINARY_SENSOR bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { - return this->client_->send_binary_sensor_info(binary_sensor); + this->client_->send_binary_sensor_info(binary_sensor); + return true; } #endif #ifdef USE_COVER -bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } +bool ListEntitiesIterator::on_cover(cover::Cover *cover) { + this->client_->send_cover_info(cover); + return true; +} #endif #ifdef USE_FAN -bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } +bool ListEntitiesIterator::on_fan(fan::Fan *fan) { + this->client_->send_fan_info(fan); + return true; +} #endif #ifdef USE_LIGHT -bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } +bool ListEntitiesIterator::on_light(light::LightState *light) { + this->client_->send_light_info(light); + return true; +} #endif #ifdef USE_SENSOR -bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); } +bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { + this->client_->send_sensor_info(sensor); + return true; +} #endif #ifdef USE_SWITCH -bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } +bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { + this->client_->send_switch_info(a_switch); + return true; +} #endif #ifdef USE_BUTTON -bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } +bool ListEntitiesIterator::on_button(button::Button *button) { + this->client_->send_button_info(button); + return true; +} #endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { - return this->client_->send_text_sensor_info(text_sensor); + this->client_->send_text_sensor_info(text_sensor); + return true; } #endif #ifdef USE_LOCK -bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } +bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { + this->client_->send_lock_info(a_lock); + return true; +} #endif #ifdef USE_VALVE -bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); } +bool ListEntitiesIterator::on_valve(valve::Valve *valve) { + this->client_->send_valve_info(valve); + return true; +} #endif -bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } +bool ListEntitiesIterator::on_end() { this->client_->send_list_info_done(); } ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); @@ -51,55 +77,83 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { #ifdef USE_ESP32_CAMERA bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { - return this->client_->send_camera_info(camera); + this->client_->send_camera_info(camera); + return true; } #endif #ifdef USE_CLIMATE -bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); } +bool ListEntitiesIterator::on_climate(climate::Climate *climate) { + this->client_->send_climate_info(climate); + return true; +} #endif #ifdef USE_NUMBER -bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); } +bool ListEntitiesIterator::on_number(number::Number *number) { + this->client_->send_number_info(number); + return true; +} #endif #ifdef USE_DATETIME_DATE -bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } +bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { + this->client_->send_date_info(date); + return true; +} #endif #ifdef USE_DATETIME_TIME -bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); } +bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { + this->client_->send_time_info(time); + return true; +} #endif #ifdef USE_DATETIME_DATETIME bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { - return this->client_->send_datetime_info(datetime); + this->client_->send_datetime_info(datetime); + return true; } #endif #ifdef USE_TEXT -bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } +bool ListEntitiesIterator::on_text(text::Text *text) { + this->client_->send_text_info(text); + return true; +} #endif #ifdef USE_SELECT -bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } +bool ListEntitiesIterator::on_select(select::Select *select) { + this->client_->send_select_info(select); + return true; +} #endif #ifdef USE_MEDIA_PLAYER bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { - return this->client_->send_media_player_info(media_player); + this->client_->send_media_player_info(media_player); + return true; } #endif #ifdef USE_ALARM_CONTROL_PANEL bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { - return this->client_->send_alarm_control_panel_info(a_alarm_control_panel); + this->client_->send_alarm_control_panel_info(a_alarm_control_panel); + return true; } #endif #ifdef USE_EVENT -bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); } +bool ListEntitiesIterator::on_event(event::Event *event) { + this->client_->send_event_info(event); + return true; +} #endif #ifdef USE_UPDATE -bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); } +bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { + this->client_->send_update_info(update); + return true; +} #endif } // namespace api diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index a37586de0f..3f2d6af4db 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -80,6 +80,7 @@ class ListEntitiesIterator : public ComponentIterator { bool on_update(update::UpdateEntity *update) override; #endif bool on_end() override; + bool completed() { return this->state_ == IteratorState::NONE; } protected: APIConnection *client_; diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 67c4346210..1c07fbd1cb 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -76,6 +76,8 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_UPDATE bool on_update(update::UpdateEntity *update) override; #endif + bool completed() { return this->state_ == IteratorState::NONE; } + protected: APIConnection *client_; }; diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 061afcb026..a2f4ecd8ee 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -8,11 +8,13 @@ namespace event { static const char *const TAG = "event"; void Event::trigger(const std::string &event_type) { - if (types_.find(event_type) == types_.end()) { + auto found = types_.find(event_type); + if (found == types_.end()) { ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str()); return; } - ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str()); + state = &(*found); + ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), state->c_str()); this->event_callback_.call(event_type); } diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 067a867360..260cb0ce53 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -23,6 +23,8 @@ namespace event { class Event : public EntityBase, public EntityBase_DeviceClass { public: + const std::string *state; + void trigger(const std::string &event_type); void set_event_types(const std::set &event_types) { this->types_ = event_types; } std::set get_event_types() const { return this->types_; } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index bf676107c7..a4b7e9cbf6 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -166,3 +166,4 @@ // #define USE_BSEC2 // Requires a library with proprietary license #define USE_DASHBOARD_IMPORT +//#define USE_ESP32_CAMERA