mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/dev' into idf_webserver_ota
This commit is contained in:
		| @@ -332,6 +332,7 @@ esphome/components/pca6416a/* @Mat931 | ||||
| esphome/components/pca9554/* @clydebarrow @hwstar | ||||
| esphome/components/pcf85063/* @brogon | ||||
| esphome/components/pcf8563/* @KoenBreeman | ||||
| esphome/components/pi4ioe5v6408/* @jesserockz | ||||
| esphome/components/pid/* @OttoWinter | ||||
| esphome/components/pipsolar/* @andreashergert1984 | ||||
| esphome/components/pm1006/* @habbie | ||||
|   | ||||
| @@ -110,9 +110,10 @@ CONFIG_SCHEMA = cv.All( | ||||
|             ): ACTIONS_SCHEMA, | ||||
|             cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, | ||||
|             cv.Optional(CONF_ENCRYPTION): _encryption_schema, | ||||
|             cv.Optional( | ||||
|                 CONF_BATCH_DELAY, default="100ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All( | ||||
|                 cv.positive_time_period_milliseconds, | ||||
|                 cv.Range(max=cv.TimePeriod(milliseconds=65535)), | ||||
|             ), | ||||
|             cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( | ||||
|                 single=True | ||||
|             ), | ||||
|   | ||||
| @@ -93,21 +93,21 @@ APIConnection::~APIConnection() { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||
|   // Set log-only mode | ||||
|   this->log_only_mode_ = true; | ||||
|   this->flags_.log_only_mode = true; | ||||
|  | ||||
|   // Call the creator - it will create the message and log it via encode_message_to_buffer | ||||
|   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||
|  | ||||
|   // Clear log-only mode | ||||
|   this->log_only_mode_ = false; | ||||
|   this->flags_.log_only_mode = false; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void APIConnection::loop() { | ||||
|   if (this->next_close_) { | ||||
|   if (this->flags_.next_close) { | ||||
|     // requested a disconnect | ||||
|     this->helper_->close(); | ||||
|     this->remove_ = true; | ||||
|     this->flags_.remove = true; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -148,15 +148,14 @@ void APIConnection::loop() { | ||||
|         } else { | ||||
|           this->read_message(0, buffer.type, nullptr); | ||||
|         } | ||||
|         if (this->remove_) | ||||
|         if (this->flags_.remove) | ||||
|           return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Process deferred batch if scheduled | ||||
|   if (this->deferred_batch_.batch_scheduled && | ||||
|       now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||
|   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||
|     this->process_batch_(); | ||||
|   } | ||||
|  | ||||
| @@ -166,7 +165,7 @@ void APIConnection::loop() { | ||||
|     this->initial_state_iterator_.advance(); | ||||
|   } | ||||
|  | ||||
|   if (this->sent_ping_) { | ||||
|   if (this->flags_.sent_ping) { | ||||
|     // Disconnect if not responded within 2.5*keepalive | ||||
|     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { | ||||
|       on_fatal_error(); | ||||
| @@ -174,13 +173,13 @@ void APIConnection::loop() { | ||||
|     } | ||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) { | ||||
|     ESP_LOGVV(TAG, "Sending keepalive PING"); | ||||
|     this->sent_ping_ = this->send_message(PingRequest()); | ||||
|     if (!this->sent_ping_) { | ||||
|     this->flags_.sent_ping = this->send_message(PingRequest()); | ||||
|     if (!this->flags_.sent_ping) { | ||||
|       // If we can't send the ping request directly (tx_buffer full), | ||||
|       // schedule it at the front of the batch so it will be sent with priority | ||||
|       ESP_LOGW(TAG, "Buffer full, ping queued"); | ||||
|       this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); | ||||
|       this->sent_ping_ = true;  // Mark as sent to avoid scheduling multiple pings | ||||
|       this->flags_.sent_ping = true;  // Mark as sent to avoid scheduling multiple pings | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -240,13 +239,13 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { | ||||
|   // don't close yet, we still need to send the disconnect response | ||||
|   // close will happen on next loop | ||||
|   ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); | ||||
|   this->next_close_ = true; | ||||
|   this->flags_.next_close = true; | ||||
|   DisconnectResponse resp; | ||||
|   return resp; | ||||
| } | ||||
| void APIConnection::on_disconnect_response(const DisconnectResponse &value) { | ||||
|   this->helper_->close(); | ||||
|   this->remove_ = true; | ||||
|   this->flags_.remove = true; | ||||
| } | ||||
|  | ||||
| // Encodes a message to the buffer and returns the total number of bytes used, | ||||
| @@ -255,7 +254,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | ||||
|                                                  uint32_t remaining_size, bool is_single) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   // If in log-only mode, just log and return | ||||
|   if (conn->log_only_mode_) { | ||||
|   if (conn->flags_.log_only_mode) { | ||||
|     conn->log_send_message_(msg.message_name(), msg.dump()); | ||||
|     return 1;  // Return non-zero to indicate "success" for logging | ||||
|   } | ||||
| @@ -304,10 +303,6 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary | ||||
|   return this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_state, | ||||
|                                  BinarySensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_info, | ||||
|                           ListEntitiesBinarySensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                      bool is_single) { | ||||
| @@ -335,9 +330,6 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne | ||||
| bool APIConnection::send_cover_state(cover::Cover *cover) { | ||||
|   return this->schedule_message_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_cover_info(cover::Cover *cover) { | ||||
|   this->schedule_message_(cover, &APIConnection::try_send_cover_info, ListEntitiesCoverResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *cover = static_cast<cover::Cover *>(entity); | ||||
| @@ -399,9 +391,6 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { | ||||
| bool APIConnection::send_fan_state(fan::Fan *fan) { | ||||
|   return this->schedule_message_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_fan_info(fan::Fan *fan) { | ||||
|   this->schedule_message_(fan, &APIConnection::try_send_fan_info, ListEntitiesFanResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *fan = static_cast<fan::Fan *>(entity); | ||||
| @@ -461,9 +450,6 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
| bool APIConnection::send_light_state(light::LightState *light) { | ||||
|   return this->schedule_message_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_light_info(light::LightState *light) { | ||||
|   this->schedule_message_(light, &APIConnection::try_send_light_info, ListEntitiesLightResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *light = static_cast<light::LightState *>(entity); | ||||
| @@ -556,9 +542,6 @@ void APIConnection::light_command(const LightCommandRequest &msg) { | ||||
| bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | ||||
|   return this->schedule_message_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||
|   this->schedule_message_(sensor, &APIConnection::try_send_sensor_info, ListEntitiesSensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -591,9 +574,6 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * | ||||
| bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | ||||
|   return this->schedule_message_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||
|   this->schedule_message_(a_switch, &APIConnection::try_send_switch_info, ListEntitiesSwitchResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -632,10 +612,6 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) | ||||
|   return this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_state, | ||||
|                                  TextSensorStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) { | ||||
|   this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_info, | ||||
|                           ListEntitiesTextSensorResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                    bool is_single) { | ||||
| @@ -696,9 +672,6 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection | ||||
|     resp.target_humidity = climate->target_humidity; | ||||
|   return encode_message_to_buffer(resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|   this->schedule_message_(climate, &APIConnection::try_send_climate_info, ListEntitiesClimateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
|   auto *climate = static_cast<climate::Climate *>(entity); | ||||
| @@ -766,9 +739,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | ||||
| bool APIConnection::send_number_state(number::Number *number) { | ||||
|   return this->schedule_message_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_number_info(number::Number *number) { | ||||
|   this->schedule_message_(number, &APIConnection::try_send_number_info, ListEntitiesNumberResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -820,9 +790,6 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c | ||||
|   fill_entity_state_base(date, resp); | ||||
|   return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_date_info(datetime::DateEntity *date) { | ||||
|   this->schedule_message_(date, &APIConnection::try_send_date_info, ListEntitiesDateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *date = static_cast<datetime::DateEntity *>(entity); | ||||
| @@ -857,9 +824,6 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c | ||||
|   fill_entity_state_base(time, resp); | ||||
|   return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_time_info(datetime::TimeEntity *time) { | ||||
|   this->schedule_message_(time, &APIConnection::try_send_time_info, ListEntitiesTimeResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                            bool is_single) { | ||||
|   auto *time = static_cast<datetime::TimeEntity *>(entity); | ||||
| @@ -896,9 +860,6 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio | ||||
|   fill_entity_state_base(datetime, resp); | ||||
|   return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | ||||
|   this->schedule_message_(datetime, &APIConnection::try_send_datetime_info, ListEntitiesDateTimeResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                bool is_single) { | ||||
|   auto *datetime = static_cast<datetime::DateTimeEntity *>(entity); | ||||
| @@ -922,9 +883,6 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | ||||
| bool APIConnection::send_text_state(text::Text *text) { | ||||
|   return this->schedule_message_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_text_info(text::Text *text) { | ||||
|   this->schedule_message_(text, &APIConnection::try_send_text_info, ListEntitiesTextResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
| @@ -963,9 +921,6 @@ void APIConnection::text_command(const TextCommandRequest &msg) { | ||||
| bool APIConnection::send_select_state(select::Select *select) { | ||||
|   return this->schedule_message_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_select_info(select::Select *select) { | ||||
|   this->schedule_message_(select, &APIConnection::try_send_select_info, ListEntitiesSelectResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                               bool is_single) { | ||||
| @@ -999,9 +954,6 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BUTTON | ||||
| void esphome::api::APIConnection::send_button_info(button::Button *button) { | ||||
|   this->schedule_message_(button, &APIConnection::try_send_button_info, ListEntitiesButtonResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *button = static_cast<button::Button *>(entity); | ||||
| @@ -1024,9 +976,6 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg | ||||
| bool APIConnection::send_lock_state(lock::Lock *a_lock) { | ||||
|   return this->schedule_message_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_lock_info(lock::Lock *a_lock) { | ||||
|   this->schedule_message_(a_lock, &APIConnection::try_send_lock_info, ListEntitiesLockResponse::MESSAGE_TYPE); | ||||
| } | ||||
|  | ||||
| uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
| @@ -1080,9 +1029,6 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection * | ||||
|   fill_entity_state_base(valve, resp); | ||||
|   return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_valve_info(valve::Valve *valve) { | ||||
|   this->schedule_message_(valve, &APIConnection::try_send_valve_info, ListEntitiesValveResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                             bool is_single) { | ||||
|   auto *valve = static_cast<valve::Valve *>(entity); | ||||
| @@ -1128,10 +1074,6 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne | ||||
|   fill_entity_state_base(media_player, resp); | ||||
|   return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | ||||
|   this->schedule_message_(media_player, &APIConnection::try_send_media_player_info, | ||||
|                           ListEntitiesMediaPlayerResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                                    bool is_single) { | ||||
|   auto *media_player = static_cast<media_player::MediaPlayer *>(entity); | ||||
| @@ -1175,7 +1117,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||
|   if (!this->state_subscription_) | ||||
|   if (!this->flags_.state_subscription) | ||||
|     return; | ||||
|   if (this->image_reader_.available()) | ||||
|     return; | ||||
| @@ -1183,9 +1125,6 @@ void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> | ||||
|       image->was_requested_by(esphome::esp32_camera::IDLE)) | ||||
|     this->image_reader_.set_image(std::move(image)); | ||||
| } | ||||
| void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||
|   this->schedule_message_(camera, &APIConnection::try_send_camera_info, ListEntitiesCameraResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity); | ||||
| @@ -1392,10 +1331,6 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A | ||||
|   fill_entity_state_base(a_alarm_control_panel, resp); | ||||
|   return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||
|   this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_info, | ||||
|                           ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, | ||||
|                                                           uint32_t remaining_size, bool is_single) { | ||||
|   auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity); | ||||
| @@ -1446,9 +1381,6 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | ||||
| void APIConnection::send_event(event::Event *event, const std::string &event_type) { | ||||
|   this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); | ||||
| } | ||||
| void APIConnection::send_event_info(event::Event *event) { | ||||
|   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, | ||||
|                                                 uint32_t remaining_size, bool is_single) { | ||||
|   EventResponse resp; | ||||
| @@ -1494,9 +1426,6 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection | ||||
|   fill_entity_state_base(update, resp); | ||||
|   return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||
| } | ||||
| void APIConnection::send_update_info(update::UpdateEntity *update) { | ||||
|   this->schedule_message_(update, &APIConnection::try_send_update_info, ListEntitiesUpdateResponse::MESSAGE_TYPE); | ||||
| } | ||||
| uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                              bool is_single) { | ||||
|   auto *update = static_cast<update::UpdateEntity *>(entity); | ||||
| @@ -1529,7 +1458,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) { | ||||
| #endif | ||||
|  | ||||
| bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) { | ||||
|   if (this->log_subscription_ < level) | ||||
|   if (this->flags_.log_subscription < level) | ||||
|     return false; | ||||
|  | ||||
|   // Pre-calculate message size to avoid reallocations | ||||
| @@ -1570,7 +1499,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||
|   resp.name = App.get_name(); | ||||
|  | ||||
|   this->connection_state_ = ConnectionState::CONNECTED; | ||||
|   this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED); | ||||
|   return resp; | ||||
| } | ||||
| ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ||||
| @@ -1581,7 +1510,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | ||||
|   resp.invalid_password = !correct; | ||||
|   if (correct) { | ||||
|     ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); | ||||
|     this->connection_state_ = ConnectionState::AUTHENTICATED; | ||||
|     this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); | ||||
|     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|     if (homeassistant::global_homeassistant_time != nullptr) { | ||||
| @@ -1695,7 +1624,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant | ||||
|   state_subs_at_ = 0; | ||||
| } | ||||
| bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | ||||
|   if (this->remove_) | ||||
|   if (this->flags_.remove) | ||||
|     return false; | ||||
|   if (this->helper_->can_write_without_blocking()) | ||||
|     return true; | ||||
| @@ -1745,7 +1674,7 @@ void APIConnection::on_no_setup_connection() { | ||||
| } | ||||
| void APIConnection::on_fatal_error() { | ||||
|   this->helper_->close(); | ||||
|   this->remove_ = true; | ||||
|   this->flags_.remove = true; | ||||
| } | ||||
|  | ||||
| void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||
| @@ -1770,8 +1699,8 @@ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCre | ||||
| } | ||||
|  | ||||
| bool APIConnection::schedule_batch_() { | ||||
|   if (!this->deferred_batch_.batch_scheduled) { | ||||
|     this->deferred_batch_.batch_scheduled = true; | ||||
|   if (!this->flags_.batch_scheduled) { | ||||
|     this->flags_.batch_scheduled = true; | ||||
|     this->deferred_batch_.batch_start_time = App.get_loop_component_start_time(); | ||||
|   } | ||||
|   return true; | ||||
| @@ -1780,14 +1709,14 @@ bool APIConnection::schedule_batch_() { | ||||
| ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); } | ||||
|  | ||||
| ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) { | ||||
|   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->batch_first_message_); | ||||
|   this->batch_first_message_ = false; | ||||
|   ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message); | ||||
|   this->flags_.batch_first_message = false; | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| void APIConnection::process_batch_() { | ||||
|   if (this->deferred_batch_.empty()) { | ||||
|     this->deferred_batch_.batch_scheduled = false; | ||||
|     this->flags_.batch_scheduled = false; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| @@ -1840,7 +1769,7 @@ void APIConnection::process_batch_() { | ||||
|  | ||||
|   // Reserve based on estimated size (much more accurate than 24-byte worst-case) | ||||
|   this->parent_->get_shared_buffer_ref().reserve(total_estimated_size + total_overhead); | ||||
|   this->batch_first_message_ = true; | ||||
|   this->flags_.batch_first_message = true; | ||||
|  | ||||
|   size_t items_processed = 0; | ||||
|   uint16_t remaining_size = std::numeric_limits<uint16_t>::max(); | ||||
|   | ||||
| @@ -22,6 +22,7 @@ static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | ||||
| class APIConnection : public APIServerConnection { | ||||
|  public: | ||||
|   friend class APIServer; | ||||
|   friend class ListEntitiesIterator; | ||||
|   APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); | ||||
|   virtual ~APIConnection(); | ||||
|  | ||||
| @@ -34,98 +35,79 @@ class APIConnection : public APIServerConnection { | ||||
|   } | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); | ||||
|   void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_cover_state(cover::Cover *cover); | ||||
|   void send_cover_info(cover::Cover *cover); | ||||
|   void cover_command(const CoverCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state(fan::Fan *fan); | ||||
|   void send_fan_info(fan::Fan *fan); | ||||
|   void fan_command(const FanCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_light_state(light::LightState *light); | ||||
|   void send_light_info(light::LightState *light); | ||||
|   void light_command(const LightCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_sensor_state(sensor::Sensor *sensor); | ||||
|   void send_sensor_info(sensor::Sensor *sensor); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_switch_state(switch_::Switch *a_switch); | ||||
|   void send_switch_info(switch_::Switch *a_switch); | ||||
|   void switch_command(const SwitchCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); | ||||
|   void send_text_sensor_info(text_sensor::TextSensor *text_sensor); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); | ||||
|   void send_camera_info(esp32_camera::ESP32Camera *camera); | ||||
|   void camera_image(const CameraImageRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_climate_state(climate::Climate *climate); | ||||
|   void send_climate_info(climate::Climate *climate); | ||||
|   void climate_command(const ClimateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool send_number_state(number::Number *number); | ||||
|   void send_number_info(number::Number *number); | ||||
|   void number_command(const NumberCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool send_date_state(datetime::DateEntity *date); | ||||
|   void send_date_info(datetime::DateEntity *date); | ||||
|   void date_command(const DateCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool send_time_state(datetime::TimeEntity *time); | ||||
|   void send_time_info(datetime::TimeEntity *time); | ||||
|   void time_command(const TimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool send_datetime_state(datetime::DateTimeEntity *datetime); | ||||
|   void send_datetime_info(datetime::DateTimeEntity *datetime); | ||||
|   void datetime_command(const DateTimeCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool send_text_state(text::Text *text); | ||||
|   void send_text_info(text::Text *text); | ||||
|   void text_command(const TextCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool send_select_state(select::Select *select); | ||||
|   void send_select_info(select::Select *select); | ||||
|   void select_command(const SelectCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   void send_button_info(button::Button *button); | ||||
|   void button_command(const ButtonCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool send_lock_state(lock::Lock *a_lock); | ||||
|   void send_lock_info(lock::Lock *a_lock); | ||||
|   void lock_command(const LockCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool send_valve_state(valve::Valve *valve); | ||||
|   void send_valve_info(valve::Valve *valve); | ||||
|   void valve_command(const ValveCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool send_media_player_state(media_player::MediaPlayer *media_player); | ||||
|   void send_media_player_info(media_player::MediaPlayer *media_player); | ||||
|   void media_player_command(const MediaPlayerCommandRequest &msg) override; | ||||
| #endif | ||||
|   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_) | ||||
|     if (!this->flags_.service_call_subscription) | ||||
|       return; | ||||
|     this->send_message(call); | ||||
|   } | ||||
| @@ -167,25 +149,22 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||
|   void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); | ||||
|   void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_EVENT | ||||
|   void send_event(event::Event *event, const std::string &event_type); | ||||
|   void send_event_info(event::Event *event); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_UPDATE | ||||
|   bool send_update_state(update::UpdateEntity *update); | ||||
|   void send_update_info(update::UpdateEntity *update); | ||||
|   void update_command(const UpdateCommandRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
|   void on_disconnect_response(const DisconnectResponse &value) override; | ||||
|   void on_ping_response(const PingResponse &value) override { | ||||
|     // we initiated ping | ||||
|     this->sent_ping_ = false; | ||||
|     this->flags_.sent_ping = false; | ||||
|   } | ||||
|   void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
| @@ -198,16 +177,16 @@ class APIConnection : public APIServerConnection { | ||||
|   DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; | ||||
|   void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } | ||||
|   void subscribe_states(const SubscribeStatesRequest &msg) override { | ||||
|     this->state_subscription_ = true; | ||||
|     this->flags_.state_subscription = true; | ||||
|     this->initial_state_iterator_.begin(); | ||||
|   } | ||||
|   void subscribe_logs(const SubscribeLogsRequest &msg) override { | ||||
|     this->log_subscription_ = msg.level; | ||||
|     this->flags_.log_subscription = msg.level; | ||||
|     if (msg.dump_config) | ||||
|       App.schedule_dump_config(); | ||||
|   } | ||||
|   void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { | ||||
|     this->service_call_subscription_ = true; | ||||
|     this->flags_.service_call_subscription = true; | ||||
|   } | ||||
|   void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||
|   GetTimeResponse get_time(const GetTimeRequest &msg) override { | ||||
| @@ -219,9 +198,12 @@ class APIConnection : public APIServerConnection { | ||||
|   NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; | ||||
| #endif | ||||
|  | ||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||
|   bool is_authenticated() override { | ||||
|     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; | ||||
|   } | ||||
|   bool is_connection_setup() override { | ||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||
|     return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED || | ||||
|            this->is_authenticated(); | ||||
|   } | ||||
|   void on_fatal_error() override; | ||||
|   void on_unauthenticated_access() override; | ||||
| @@ -444,49 +426,28 @@ class APIConnection : public APIServerConnection { | ||||
|   static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||
|                                         bool is_single); | ||||
|  | ||||
|   // Pointers first (4 bytes each, naturally aligned) | ||||
|   // === Optimal member ordering for 32-bit systems === | ||||
|  | ||||
|   // Group 1: Pointers (4 bytes each on 32-bit) | ||||
|   std::unique_ptr<APIFrameHelper> helper_; | ||||
|   APIServer *parent_; | ||||
|  | ||||
|   // 4-byte aligned types | ||||
|   uint32_t last_traffic_; | ||||
|   int state_subs_at_ = -1; | ||||
|  | ||||
|   // Strings (12 bytes each on 32-bit) | ||||
|   std::string client_info_; | ||||
|   std::string client_peername_; | ||||
|  | ||||
|   // 2-byte aligned types | ||||
|   uint16_t client_api_version_major_{0}; | ||||
|   uint16_t client_api_version_minor_{0}; | ||||
|  | ||||
|   // Group all 1-byte types together to minimize padding | ||||
|   enum class ConnectionState : uint8_t { | ||||
|     WAITING_FOR_HELLO, | ||||
|     CONNECTED, | ||||
|     AUTHENTICATED, | ||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; | ||||
|   uint8_t log_subscription_{ESPHOME_LOG_LEVEL_NONE}; | ||||
|   bool remove_{false}; | ||||
|   bool state_subscription_{false}; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
|   bool next_close_ = false; | ||||
|   // 7 bytes used, 1 byte padding | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   // When true, encode_message_to_buffer will only log, not encode | ||||
|   bool log_only_mode_{false}; | ||||
| #endif | ||||
|   uint8_t ping_retries_{0}; | ||||
|   // 8 bytes used, no padding needed | ||||
|  | ||||
|   // Larger objects at the end | ||||
|   // Group 2: Larger objects (must be 4-byte aligned) | ||||
|   // These contain vectors/pointers internally, so putting them early ensures good alignment | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   ListEntitiesIterator list_entities_iterator_; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   esp32_camera::CameraImageReader image_reader_; | ||||
| #endif | ||||
|  | ||||
|   // Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) | ||||
|   std::string client_info_; | ||||
|   std::string client_peername_; | ||||
|  | ||||
|   // Group 4: 4-byte types | ||||
|   uint32_t last_traffic_; | ||||
|   int state_subs_at_ = -1; | ||||
|  | ||||
|   // Function pointer type for message encoding | ||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||
|  | ||||
| @@ -596,7 +557,6 @@ class APIConnection : public APIServerConnection { | ||||
|  | ||||
|     std::vector<BatchItem> items; | ||||
|     uint32_t batch_start_time{0}; | ||||
|     bool batch_scheduled{false}; | ||||
|  | ||||
|     DeferredBatch() { | ||||
|       // Pre-allocate capacity for typical batch sizes to avoid reallocation | ||||
| @@ -609,13 +569,47 @@ class APIConnection : public APIServerConnection { | ||||
|     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||
|     void clear() { | ||||
|       items.clear(); | ||||
|       batch_scheduled = false; | ||||
|       batch_start_time = 0; | ||||
|     } | ||||
|     bool empty() const { return items.empty(); } | ||||
|   }; | ||||
|  | ||||
|   // DeferredBatch here (16 bytes, 4-byte aligned) | ||||
|   DeferredBatch deferred_batch_; | ||||
|  | ||||
|   // ConnectionState enum for type safety | ||||
|   enum class ConnectionState : uint8_t { | ||||
|     WAITING_FOR_HELLO = 0, | ||||
|     CONNECTED = 1, | ||||
|     AUTHENTICATED = 2, | ||||
|   }; | ||||
|  | ||||
|   // Group 5: Pack all small members together to minimize padding | ||||
|   // This group starts at a 4-byte boundary after DeferredBatch | ||||
|   struct APIFlags { | ||||
|     // Connection state only needs 2 bits (3 states) | ||||
|     uint8_t connection_state : 2; | ||||
|     // Log subscription needs 3 bits (log levels 0-7) | ||||
|     uint8_t log_subscription : 3; | ||||
|     // Boolean flags (1 bit each) | ||||
|     uint8_t remove : 1; | ||||
|     uint8_t state_subscription : 1; | ||||
|     uint8_t sent_ping : 1; | ||||
|  | ||||
|     uint8_t service_call_subscription : 1; | ||||
|     uint8_t next_close : 1; | ||||
|     uint8_t batch_scheduled : 1; | ||||
|     uint8_t batch_first_message : 1;  // For batch buffer allocation | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|     uint8_t log_only_mode : 1; | ||||
| #endif | ||||
|   } flags_{};  // 2 bytes total | ||||
|  | ||||
|   // 2-byte types immediately after flags_ (no padding between them) | ||||
|   uint16_t client_api_version_major_{0}; | ||||
|   uint16_t client_api_version_minor_{0}; | ||||
|   // Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary | ||||
|  | ||||
|   uint32_t get_batch_delay_ms_() const; | ||||
|   // Message will use 8 more bytes than the minimum size, and typical | ||||
|   // MTU is 1500. Sometimes users will see as low as 1460 MTU. | ||||
| @@ -633,9 +627,6 @@ class APIConnection : public APIServerConnection { | ||||
|   bool schedule_batch_(); | ||||
|   void process_batch_(); | ||||
|  | ||||
|   // State for batch buffer allocation | ||||
|   bool batch_first_message_{false}; | ||||
|  | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void log_batch_item_(const DeferredBatch::BatchItem &item); | ||||
| #endif | ||||
|   | ||||
| @@ -14,7 +14,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
| void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
|       HelloRequest msg; | ||||
| @@ -106,50 +106,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       this->on_subscribe_logs_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 30: { | ||||
| #ifdef USE_COVER | ||||
|     case 30: { | ||||
|       CoverCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_cover_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 31: { | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|     case 31: { | ||||
|       FanCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_fan_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 32: { | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|     case 32: { | ||||
|       LightCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_light_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 33: { | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|     case 33: { | ||||
|       SwitchCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_switch_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
|     case 34: { | ||||
|       SubscribeHomeassistantServicesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| @@ -204,395 +204,394 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | ||||
|       this->on_execute_service_request(msg); | ||||
|       break; | ||||
|     } | ||||
|     case 45: { | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|     case 45: { | ||||
|       CameraImageRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_camera_image_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 48: { | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|     case 48: { | ||||
|       ClimateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_climate_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 51: { | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|     case 51: { | ||||
|       NumberCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_number_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 54: { | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|     case 54: { | ||||
|       SelectCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_select_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 57: { | ||||
| #endif | ||||
| #ifdef USE_SIREN | ||||
|     case 57: { | ||||
|       SirenCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_siren_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 60: { | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|     case 60: { | ||||
|       LockCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_lock_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 62: { | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|     case 62: { | ||||
|       ButtonCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_button_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 65: { | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|     case 65: { | ||||
|       MediaPlayerCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_media_player_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 66: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 66: { | ||||
|       SubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 68: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 68: { | ||||
|       BluetoothDeviceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_device_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 70: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 70: { | ||||
|       BluetoothGATTGetServicesRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_get_services_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 73: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 73: { | ||||
|       BluetoothGATTReadRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_read_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 75: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 75: { | ||||
|       BluetoothGATTWriteRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_write_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 76: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 76: { | ||||
|       BluetoothGATTReadDescriptorRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_read_descriptor_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 77: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 77: { | ||||
|       BluetoothGATTWriteDescriptorRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_write_descriptor_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 78: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 78: { | ||||
|       BluetoothGATTNotifyRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_gatt_notify_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 80: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 80: { | ||||
|       SubscribeBluetoothConnectionsFreeRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_bluetooth_connections_free_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 87: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 87: { | ||||
|       UnsubscribeBluetoothLEAdvertisementsRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_unsubscribe_bluetooth_le_advertisements_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 89: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 89: { | ||||
|       SubscribeVoiceAssistantRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_subscribe_voice_assistant_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 91: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 91: { | ||||
|       VoiceAssistantResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 92: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 92: { | ||||
|       VoiceAssistantEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 96: { | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|     case 96: { | ||||
|       AlarmControlPanelCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_alarm_control_panel_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 99: { | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|     case 99: { | ||||
|       TextCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_text_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 102: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|     case 102: { | ||||
|       DateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 105: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|     case 105: { | ||||
|       TimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 106: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 106: { | ||||
|       VoiceAssistantAudio msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_audio(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 111: { | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|     case 111: { | ||||
|       ValveCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_valve_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 114: { | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|     case 114: { | ||||
|       DateTimeCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_date_time_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 115: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 115: { | ||||
|       VoiceAssistantTimerEventResponse msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_timer_event_response(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 118: { | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|     case 118: { | ||||
|       UpdateCommandRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_update_command_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 119: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 119: { | ||||
|       VoiceAssistantAnnounceRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_announce_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 121: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 121: { | ||||
|       VoiceAssistantConfigurationRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_configuration_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 123: { | ||||
| #endif | ||||
| #ifdef USE_VOICE_ASSISTANT | ||||
|     case 123: { | ||||
|       VoiceAssistantSetConfiguration msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_voice_assistant_set_configuration(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 124: { | ||||
| #endif | ||||
| #ifdef USE_API_NOISE | ||||
|     case 124: { | ||||
|       NoiseEncryptionSetKeyRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_noise_encryption_set_key_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
|     case 127: { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|     case 127: { | ||||
|       BluetoothScannerSetModeRequest msg; | ||||
|       msg.decode(msg_data, msg_size); | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|       ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|       this->on_bluetooth_scanner_set_mode_request(msg); | ||||
| #endif | ||||
|       break; | ||||
|     } | ||||
| #endif | ||||
|     default: | ||||
|       return false; | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void APIServerConnection::on_hello_request(const HelloRequest &msg) { | ||||
|   | ||||
| @@ -199,7 +199,7 @@ class APIServerConnectionBase : public ProtoService { | ||||
|   virtual void on_update_command_request(const UpdateCommandRequest &value){}; | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
|   void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
| }; | ||||
|  | ||||
| class APIServerConnection : public APIServerConnectionBase { | ||||
|   | ||||
| @@ -316,15 +316,13 @@ class ProtoSize { | ||||
|   /** | ||||
|    * @brief Calculates and adds the size of a nested message field to the total message size | ||||
|    * | ||||
|    * This templated version directly takes a message object, calculates its size internally, | ||||
|    * This version takes a ProtoMessage object, calculates its size internally, | ||||
|    * and updates the total_size reference. This eliminates the need for a temporary variable | ||||
|    * at the call site. | ||||
|    * | ||||
|    * @tparam MessageType The type of the nested message (inferred from parameter) | ||||
|    * @param message The nested message object | ||||
|    */ | ||||
|   template<typename MessageType> | ||||
|   static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message, | ||||
|   static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message, | ||||
|                                         bool force = false) { | ||||
|     uint32_t nested_size = 0; | ||||
|     message.calculate_size(nested_size); | ||||
|   | ||||
| @@ -104,7 +104,7 @@ void APIServer::setup() { | ||||
|         return; | ||||
|       } | ||||
|       for (auto &c : this->clients_) { | ||||
|         if (!c->remove_) | ||||
|         if (!c->flags_.remove) | ||||
|           c->try_send_log_message(level, tag, message); | ||||
|       } | ||||
|     }); | ||||
| @@ -116,7 +116,7 @@ void APIServer::setup() { | ||||
|     esp32_camera::global_esp32_camera->add_image_callback( | ||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||
|           for (auto &c : this->clients_) { | ||||
|             if (!c->remove_) | ||||
|             if (!c->flags_.remove) | ||||
|               c->set_camera_state(image); | ||||
|           } | ||||
|         }); | ||||
| @@ -176,7 +176,7 @@ void APIServer::loop() { | ||||
|   while (client_index < this->clients_.size()) { | ||||
|     auto &client = this->clients_[client_index]; | ||||
|  | ||||
|     if (!client->remove_) { | ||||
|     if (!client->flags_.remove) { | ||||
|       // Common case: process active client | ||||
|       client->loop(); | ||||
|       client_index++; | ||||
| @@ -431,7 +431,7 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; } | ||||
|  | ||||
| void APIServer::set_password(const std::string &password) { this->password_ = password; } | ||||
|  | ||||
| void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; } | ||||
| void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } | ||||
|  | ||||
| void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||
|   for (auto &client : this->clients_) { | ||||
| @@ -502,7 +502,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
| void APIServer::request_time() { | ||||
|   for (auto &client : this->clients_) { | ||||
|     if (!client->remove_ && client->is_authenticated()) | ||||
|     if (!client->flags_.remove && client->is_authenticated()) | ||||
|       client->send_time_request(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -40,8 +40,8 @@ class APIServer : public Component, public Controller { | ||||
|   void set_port(uint16_t port); | ||||
|   void set_password(const std::string &password); | ||||
|   void set_reboot_timeout(uint32_t reboot_timeout); | ||||
|   void set_batch_delay(uint32_t batch_delay); | ||||
|   uint32_t get_batch_delay() const { return batch_delay_; } | ||||
|   void set_batch_delay(uint16_t batch_delay); | ||||
|   uint16_t get_batch_delay() const { return batch_delay_; } | ||||
|  | ||||
|   // Get reference to shared buffer for API connections | ||||
|   std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; } | ||||
| @@ -150,7 +150,6 @@ class APIServer : public Component, public Controller { | ||||
|  | ||||
|   // 4-byte aligned types | ||||
|   uint32_t reboot_timeout_{300000}; | ||||
|   uint32_t batch_delay_{100}; | ||||
|  | ||||
|   // Vectors and strings (12 bytes each on 32-bit) | ||||
|   std::vector<std::unique_ptr<APIConnection>> clients_; | ||||
| @@ -161,8 +160,9 @@ class APIServer : public Component, public Controller { | ||||
|  | ||||
|   // Group smaller types together | ||||
|   uint16_t port_{6053}; | ||||
|   uint16_t batch_delay_{100}; | ||||
|   bool shutting_down_ = false; | ||||
|   // 3 bytes used, 1 byte padding | ||||
|   // 5 bytes used, 3 bytes padding | ||||
|  | ||||
| #ifdef USE_API_NOISE | ||||
|   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "list_entities.h" | ||||
| #ifdef USE_API | ||||
| #include "api_connection.h" | ||||
| #include "api_pb2.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/util.h" | ||||
| @@ -8,155 +9,85 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Generate entity handler implementations using macros | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   this->client_->send_binary_sensor_info(binary_sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(binary_sensor, binary_sensor::BinarySensor, ListEntitiesBinarySensorResponse) | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { | ||||
|   this->client_->send_cover_info(cover); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(cover, cover::Cover, ListEntitiesCoverResponse) | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool ListEntitiesIterator::on_fan(fan::Fan *fan) { | ||||
|   this->client_->send_fan_info(fan); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(fan, fan::Fan, ListEntitiesFanResponse) | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { | ||||
|   this->client_->send_light_info(light); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(light, light::LightState, ListEntitiesLightResponse) | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { | ||||
|   this->client_->send_sensor_info(sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(sensor, sensor::Sensor, ListEntitiesSensorResponse) | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { | ||||
|   this->client_->send_switch_info(a_switch); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(switch, switch_::Switch, ListEntitiesSwitchResponse) | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
| bool ListEntitiesIterator::on_button(button::Button *button) { | ||||
|   this->client_->send_button_info(button); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(button, button::Button, ListEntitiesButtonResponse) | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   this->client_->send_text_sensor_info(text_sensor); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(text_sensor, text_sensor::TextSensor, ListEntitiesTextSensorResponse) | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
| bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { | ||||
|   this->client_->send_lock_info(a_lock); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse) | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool ListEntitiesIterator::on_valve(valve::Valve *valve) { | ||||
|   this->client_->send_valve_info(valve); | ||||
|   return true; | ||||
| } | ||||
| LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse) | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| LIST_ENTITIES_HANDLER(camera, esp32_camera::ESP32Camera, ListEntitiesCameraResponse) | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse) | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| LIST_ENTITIES_HANDLER(number, number::Number, ListEntitiesNumberResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
| LIST_ENTITIES_HANDLER(date, datetime::DateEntity, ListEntitiesDateResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| LIST_ENTITIES_HANDLER(time, datetime::TimeEntity, ListEntitiesTimeResponse) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| LIST_ENTITIES_HANDLER(datetime, datetime::DateTimeEntity, ListEntitiesDateTimeResponse) | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| LIST_ENTITIES_HANDLER(text, text::Text, ListEntitiesTextResponse) | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| LIST_ENTITIES_HANDLER(select, select::Select, ListEntitiesSelectResponse) | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMediaPlayerResponse) | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel, | ||||
|                       ListEntitiesAlarmControlPanelResponse) | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
| LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse) | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| LIST_ENTITIES_HANDLER(update, update::UpdateEntity, ListEntitiesUpdateResponse) | ||||
| #endif | ||||
|  | ||||
| // Special cases that don't follow the pattern | ||||
| bool ListEntitiesIterator::on_end() { return 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(); | ||||
|   return this->client_->send_message(resp); | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | ||||
|   this->client_->send_camera_info(camera); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_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) { | ||||
|   this->client_->send_number_info(number); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_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) { | ||||
|   this->client_->send_time_info(time); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   this->client_->send_datetime_info(datetime); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_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) { | ||||
|   this->client_->send_select_info(select); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *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) { | ||||
|   this->client_->send_alarm_control_panel_info(a_alarm_control_panel); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_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) { | ||||
|   this->client_->send_update_info(update); | ||||
|   return true; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
| #endif | ||||
|   | ||||
| @@ -9,75 +9,83 @@ namespace api { | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| // Macro for generating ListEntitiesIterator handlers | ||||
| // Calls schedule_message_ with try_send_*_info | ||||
| #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ | ||||
|   bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ | ||||
|     return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ | ||||
|                                             ResponseType::MESSAGE_TYPE); \ | ||||
|   } | ||||
|  | ||||
| class ListEntitiesIterator : public ComponentIterator { | ||||
|  public: | ||||
|   ListEntitiesIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
|   bool on_cover(cover::Cover *entity) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
|   bool on_fan(fan::Fan *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   bool on_light(light::LightState *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
|   bool on_sensor(sensor::Sensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
|   bool on_switch(switch_::Switch *entity) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override; | ||||
|   bool on_button(button::Button *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
|   bool on_text_sensor(text_sensor::TextSensor *entity) override; | ||||
| #endif | ||||
|   bool on_service(UserServiceDescriptor *service) override; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   bool on_camera(esp32_camera::ESP32Camera *camera) override; | ||||
|   bool on_camera(esp32_camera::ESP32Camera *entity) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
|   bool on_climate(climate::Climate *entity) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool on_number(number::Number *number) override; | ||||
|   bool on_number(number::Number *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
|   bool on_date(datetime::DateEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
|   bool on_time(datetime::TimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
|   bool on_datetime(datetime::DateTimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
|   bool on_text(text::Text *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool on_select(select::Select *select) override; | ||||
|   bool on_select(select::Select *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
|   bool on_lock(lock::Lock *entity) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
|   bool on_valve(valve::Valve *entity) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
|   bool on_media_player(media_player::MediaPlayer *entity) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override; | ||||
|   bool on_event(event::Event *entity) override; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
|   bool on_update(update::UpdateEntity *entity) override; | ||||
| #endif | ||||
|   bool on_end() override; | ||||
|   bool completed() { return this->state_ == IteratorState::NONE; } | ||||
|   | ||||
| @@ -364,7 +364,7 @@ class ProtoService { | ||||
|    */ | ||||
|   virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; | ||||
|   virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0; | ||||
|   virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||
|   virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||
|  | ||||
|   // Optimized method that pre-allocates buffer based on message size | ||||
|   bool send_message_(const ProtoMessage &msg, uint16_t message_type) { | ||||
|   | ||||
| @@ -6,73 +6,67 @@ | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Generate entity handler implementations using macros | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   return this->client_->send_binary_sensor_state(binary_sensor); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(binary_sensor, binary_sensor::BinarySensor) | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } | ||||
| INITIAL_STATE_HANDLER(cover, cover::Cover) | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); } | ||||
| INITIAL_STATE_HANDLER(fan, fan::Fan) | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } | ||||
| INITIAL_STATE_HANDLER(light, light::LightState) | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_state(sensor); } | ||||
| INITIAL_STATE_HANDLER(sensor, sensor::Sensor) | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool InitialStateIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_state(a_switch); } | ||||
| INITIAL_STATE_HANDLER(switch, switch_::Switch) | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   return this->client_->send_text_sensor_state(text_sensor); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(text_sensor, text_sensor::TextSensor) | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); } | ||||
| INITIAL_STATE_HANDLER(climate, climate::Climate) | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number); } | ||||
| INITIAL_STATE_HANDLER(number, number::Number) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
| bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); } | ||||
| INITIAL_STATE_HANDLER(date, datetime::DateEntity) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
| bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); } | ||||
| INITIAL_STATE_HANDLER(time, datetime::TimeEntity) | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
| bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) { | ||||
|   return this->client_->send_datetime_state(datetime); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(datetime, datetime::DateTimeEntity) | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
| bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text); } | ||||
| INITIAL_STATE_HANDLER(text, text::Text) | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select); } | ||||
| INITIAL_STATE_HANDLER(select, select::Select) | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
| bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock); } | ||||
| INITIAL_STATE_HANDLER(lock, lock::Lock) | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
| bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); } | ||||
| INITIAL_STATE_HANDLER(valve, valve::Valve) | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
| bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) { | ||||
|   return this->client_->send_media_player_state(media_player); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer) | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
| bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||
|   return this->client_->send_alarm_control_panel_state(a_alarm_control_panel); | ||||
| } | ||||
| INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel) | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
| bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); } | ||||
| INITIAL_STATE_HANDLER(update, update::UpdateEntity) | ||||
| #endif | ||||
|  | ||||
| // Special cases (button and event) are already defined inline in subscribe_state.h | ||||
|  | ||||
| InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -10,71 +10,78 @@ namespace api { | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| // Macro for generating InitialStateIterator handlers | ||||
| // Calls send_*_state | ||||
| #define INITIAL_STATE_HANDLER(entity_type, EntityClass) \ | ||||
|   bool InitialStateIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ | ||||
|     return this->client_->send_##entity_type##_state(entity); \ | ||||
|   } | ||||
|  | ||||
| class InitialStateIterator : public ComponentIterator { | ||||
|  public: | ||||
|   InitialStateIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
|   bool on_cover(cover::Cover *entity) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
|   bool on_fan(fan::Fan *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   bool on_light(light::LightState *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
|   bool on_sensor(sensor::Sensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
|   bool on_switch(switch_::Switch *entity) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
|   bool on_text_sensor(text_sensor::TextSensor *entity) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
|   bool on_climate(climate::Climate *entity) override; | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   bool on_number(number::Number *number) override; | ||||
|   bool on_number(number::Number *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATE | ||||
|   bool on_date(datetime::DateEntity *date) override; | ||||
|   bool on_date(datetime::DateEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_TIME | ||||
|   bool on_time(datetime::TimeEntity *time) override; | ||||
|   bool on_time(datetime::TimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_DATETIME_DATETIME | ||||
|   bool on_datetime(datetime::DateTimeEntity *datetime) override; | ||||
|   bool on_datetime(datetime::DateTimeEntity *entity) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT | ||||
|   bool on_text(text::Text *text) override; | ||||
|   bool on_text(text::Text *entity) override; | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   bool on_select(select::Select *select) override; | ||||
|   bool on_select(select::Select *entity) override; | ||||
| #endif | ||||
| #ifdef USE_LOCK | ||||
|   bool on_lock(lock::Lock *a_lock) override; | ||||
|   bool on_lock(lock::Lock *entity) override; | ||||
| #endif | ||||
| #ifdef USE_VALVE | ||||
|   bool on_valve(valve::Valve *valve) override; | ||||
|   bool on_valve(valve::Valve *entity) override; | ||||
| #endif | ||||
| #ifdef USE_MEDIA_PLAYER | ||||
|   bool on_media_player(media_player::MediaPlayer *media_player) override; | ||||
|   bool on_media_player(media_player::MediaPlayer *entity) override; | ||||
| #endif | ||||
| #ifdef USE_ALARM_CONTROL_PANEL | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override; | ||||
|   bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override; | ||||
| #endif | ||||
| #ifdef USE_EVENT | ||||
|   bool on_event(event::Event *event) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_UPDATE | ||||
|   bool on_update(update::UpdateEntity *update) override; | ||||
|   bool on_update(update::UpdateEntity *entity) override; | ||||
| #endif | ||||
|   bool completed() { return this->state_ == IteratorState::NONE; } | ||||
|  | ||||
|   | ||||
| @@ -10,11 +10,24 @@ GPIOBinarySensor = gpio_ns.class_( | ||||
|     "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONF_USE_INTERRUPT = "use_interrupt" | ||||
| CONF_INTERRUPT_TYPE = "interrupt_type" | ||||
|  | ||||
| INTERRUPT_TYPES = { | ||||
|     "RISING": gpio_ns.INTERRUPT_RISING_EDGE, | ||||
|     "FALLING": gpio_ns.INTERRUPT_FALLING_EDGE, | ||||
|     "ANY": gpio_ns.INTERRUPT_ANY_EDGE, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     binary_sensor.binary_sensor_schema(GPIOBinarySensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_PIN): pins.gpio_input_pin_schema, | ||||
|             cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum( | ||||
|                 INTERRUPT_TYPES, upper=True | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| @@ -27,3 +40,7 @@ async def to_code(config): | ||||
|  | ||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||
|     cg.add(var.set_pin(pin)) | ||||
|  | ||||
|     cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT])) | ||||
|     if config[CONF_USE_INTERRUPT]: | ||||
|         cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) | ||||
|   | ||||
| @@ -6,17 +6,91 @@ namespace gpio { | ||||
|  | ||||
| static const char *const TAG = "gpio.binary_sensor"; | ||||
|  | ||||
| void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { | ||||
|   bool new_state = arg->isr_pin_.digital_read(); | ||||
|   if (new_state != arg->last_state_) { | ||||
|     arg->state_ = new_state; | ||||
|     arg->last_state_ = new_state; | ||||
|     arg->changed_ = true; | ||||
|     // Wake up the component from its disabled loop state | ||||
|     if (arg->component_ != nullptr) { | ||||
|       arg->component_->enable_loop_soon_any_context(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) { | ||||
|   pin->setup(); | ||||
|   this->isr_pin_ = pin->to_isr(); | ||||
|   this->component_ = component; | ||||
|  | ||||
|   // Read initial state | ||||
|   this->last_state_ = pin->digital_read(); | ||||
|   this->state_ = this->last_state_; | ||||
|  | ||||
|   // Attach interrupt - from this point on, any changes will be caught by the interrupt | ||||
|   pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type); | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::setup() { | ||||
|   this->pin_->setup(); | ||||
|   this->publish_initial_state(this->pin_->digital_read()); | ||||
|   if (this->use_interrupt_ && !this->pin_->is_internal()) { | ||||
|     ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode"); | ||||
|     this->use_interrupt_ = false; | ||||
|   } | ||||
|  | ||||
|   if (this->use_interrupt_) { | ||||
|     auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_); | ||||
|     this->store_.setup(internal_pin, this->interrupt_type_, this); | ||||
|     this->publish_initial_state(this->store_.get_state()); | ||||
|   } else { | ||||
|     this->pin_->setup(); | ||||
|     this->publish_initial_state(this->pin_->digital_read()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::dump_config() { | ||||
|   LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|   const char *mode = this->use_interrupt_ ? "interrupt" : "polling"; | ||||
|   ESP_LOGCONFIG(TAG, "  Mode: %s", mode); | ||||
|   if (this->use_interrupt_) { | ||||
|     const char *interrupt_type; | ||||
|     switch (this->interrupt_type_) { | ||||
|       case gpio::INTERRUPT_RISING_EDGE: | ||||
|         interrupt_type = "RISING_EDGE"; | ||||
|         break; | ||||
|       case gpio::INTERRUPT_FALLING_EDGE: | ||||
|         interrupt_type = "FALLING_EDGE"; | ||||
|         break; | ||||
|       case gpio::INTERRUPT_ANY_EDGE: | ||||
|         interrupt_type = "ANY_EDGE"; | ||||
|         break; | ||||
|       default: | ||||
|         interrupt_type = "UNKNOWN"; | ||||
|         break; | ||||
|     } | ||||
|     ESP_LOGCONFIG(TAG, "  Interrupt Type: %s", interrupt_type); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void GPIOBinarySensor::loop() { this->publish_state(this->pin_->digital_read()); } | ||||
| void GPIOBinarySensor::loop() { | ||||
|   if (this->use_interrupt_) { | ||||
|     if (this->store_.is_changed()) { | ||||
|       // Clear the flag immediately to minimize the window where we might miss changes | ||||
|       this->store_.clear_changed(); | ||||
|       // Read the state and publish it | ||||
|       // Note: If the ISR fires between clear_changed() and get_state(), that's fine - | ||||
|       // we'll process the new change on the next loop iteration | ||||
|       bool state = this->store_.get_state(); | ||||
|       this->publish_state(state); | ||||
|     } else { | ||||
|       // No changes, disable the loop until the next interrupt | ||||
|       this->disable_loop(); | ||||
|     } | ||||
|   } else { | ||||
|     this->publish_state(this->pin_->digital_read()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   | ||||
| @@ -2,14 +2,51 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace gpio { | ||||
|  | ||||
| // Store class for ISR data (no vtables, ISR-safe) | ||||
| class GPIOBinarySensorStore { | ||||
|  public: | ||||
|   void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component); | ||||
|  | ||||
|   static void gpio_intr(GPIOBinarySensorStore *arg); | ||||
|  | ||||
|   bool get_state() const { | ||||
|     // No lock needed: state_ is atomically updated by ISR | ||||
|     // Volatile ensures we read the latest value | ||||
|     return this->state_; | ||||
|   } | ||||
|  | ||||
|   bool is_changed() const { | ||||
|     // Simple read of volatile bool - no clearing here | ||||
|     return this->changed_; | ||||
|   } | ||||
|  | ||||
|   void clear_changed() { | ||||
|     // Separate method to clear the flag | ||||
|     this->changed_ = false; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   ISRInternalGPIOPin isr_pin_; | ||||
|   volatile bool state_{false}; | ||||
|   volatile bool last_state_{false}; | ||||
|   volatile bool changed_{false}; | ||||
|   Component *component_{nullptr};  // Pointer to the component for enable_loop_soon_any_context() | ||||
| }; | ||||
|  | ||||
| class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||
|  public: | ||||
|   // No destructor needed: ESPHome components are created at boot and live forever. | ||||
|   // Interrupts are only detached on reboot when memory is cleared anyway. | ||||
|  | ||||
|   void set_pin(GPIOPin *pin) { pin_ = pin; } | ||||
|   void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; } | ||||
|   void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; } | ||||
|   // ========== INTERNAL METHODS ========== | ||||
|   // (In most use cases you won't need these) | ||||
|   /// Setup pin | ||||
| @@ -22,6 +59,9 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { | ||||
|  | ||||
|  protected: | ||||
|   GPIOPin *pin_; | ||||
|   bool use_interrupt_{true}; | ||||
|   gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; | ||||
|   GPIOBinarySensorStore store_; | ||||
| }; | ||||
|  | ||||
| }  // namespace gpio | ||||
|   | ||||
							
								
								
									
										84
									
								
								esphome/components/pi4ioe5v6408/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								esphome/components/pi4ioe5v6408/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import i2c | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_INPUT, | ||||
|     CONF_INVERTED, | ||||
|     CONF_MODE, | ||||
|     CONF_NUMBER, | ||||
|     CONF_OUTPUT, | ||||
|     CONF_PULLDOWN, | ||||
|     CONF_PULLUP, | ||||
|     CONF_RESET, | ||||
| ) | ||||
|  | ||||
| AUTO_LOAD = ["gpio_expander"] | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
|  | ||||
| pi4ioe5v6408_ns = cg.esphome_ns.namespace("pi4ioe5v6408") | ||||
| PI4IOE5V6408Component = pi4ioe5v6408_ns.class_( | ||||
|     "PI4IOE5V6408Component", cg.Component, i2c.I2CDevice | ||||
| ) | ||||
| PI4IOE5V6408GPIOPin = pi4ioe5v6408_ns.class_("PI4IOE5V6408GPIOPin", cg.GPIOPin) | ||||
|  | ||||
| CONF_PI4IOE5V6408 = "pi4ioe5v6408" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.declare_id(PI4IOE5V6408Component), | ||||
|             cv.Optional(CONF_RESET, default=True): cv.boolean, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(0x43)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_reset(config[CONF_RESET])) | ||||
|  | ||||
|  | ||||
| def validate_mode(value): | ||||
|     if not (value[CONF_INPUT] or value[CONF_OUTPUT]): | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     if value[CONF_INPUT] and value[CONF_OUTPUT]: | ||||
|         raise cv.Invalid("Mode must be either input or output") | ||||
|     return value | ||||
|  | ||||
|  | ||||
| PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( | ||||
|     PI4IOE5V6408GPIOPin, | ||||
|     cv.int_range(min=0, max=7), | ||||
|     modes=[ | ||||
|         CONF_INPUT, | ||||
|         CONF_OUTPUT, | ||||
|         CONF_PULLUP, | ||||
|         CONF_PULLDOWN, | ||||
|     ], | ||||
|     mode_validator=validate_mode, | ||||
| ).extend( | ||||
|     { | ||||
|         cv.Required(CONF_PI4IOE5V6408): cv.use_id(PI4IOE5V6408Component), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @pins.PIN_SCHEMA_REGISTRY.register(CONF_PI4IOE5V6408, PI4IOE5V6408_PIN_SCHEMA) | ||||
| async def pi4ioe5v6408_pin_schema(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_parented(var, config[CONF_PI4IOE5V6408]) | ||||
|  | ||||
|     cg.add(var.set_pin(config[CONF_NUMBER])) | ||||
|     cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||
|     cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) | ||||
|     return var | ||||
							
								
								
									
										171
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| #include "pi4ioe5v6408.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pi4ioe5v6408 { | ||||
|  | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_DEVICE_ID = 0x01; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_IO_DIR = 0x03; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_OUT_SET = 0x05; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_OUT_HIGH_IMPEDENCE = 0x07; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_IN_DEFAULT_STATE = 0x09; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_PULL_ENABLE = 0x0B; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_PULL_SELECT = 0x0D; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_IN_STATE = 0x0F; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_ENABLE_MASK = 0x11; | ||||
| static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_STATUS = 0x13; | ||||
|  | ||||
| static const char *const TAG = "pi4ioe5v6408"; | ||||
|  | ||||
| void PI4IOE5V6408Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Running setup"); | ||||
|   if (this->reset_) { | ||||
|     this->reg(PI4IOE5V6408_REGISTER_DEVICE_ID) |= 0b00000001; | ||||
|     this->reg(PI4IOE5V6408_REGISTER_OUT_HIGH_IMPEDENCE) = 0b00000000; | ||||
|   } else { | ||||
|     if (!this->read_gpio_modes_()) { | ||||
|       this->mark_failed(); | ||||
|       ESP_LOGE(TAG, "Failed to read GPIO modes"); | ||||
|       return; | ||||
|     } | ||||
|     if (!this->read_gpio_outputs_()) { | ||||
|       this->mark_failed(); | ||||
|       ESP_LOGE(TAG, "Failed to read GPIO outputs"); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| void PI4IOE5V6408Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "PI4IOE5V6408:"); | ||||
|   LOG_I2C_DEVICE(this) | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||
|   } | ||||
| } | ||||
| void PI4IOE5V6408Component::pin_mode(uint8_t pin, gpio::Flags flags) { | ||||
|   if (flags & gpio::FLAG_OUTPUT) { | ||||
|     // Set mode mask bit | ||||
|     this->mode_mask_ |= 1 << pin; | ||||
|   } else if (flags & gpio::FLAG_INPUT) { | ||||
|     // Clear mode mask bit | ||||
|     this->mode_mask_ &= ~(1 << pin); | ||||
|     if (flags & gpio::FLAG_PULLUP) { | ||||
|       this->pull_up_down_mask_ |= 1 << pin; | ||||
|       this->pull_enable_mask_ |= 1 << pin; | ||||
|     } else if (flags & gpio::FLAG_PULLDOWN) { | ||||
|       this->pull_up_down_mask_ &= ~(1 << pin); | ||||
|       this->pull_enable_mask_ |= 1 << pin; | ||||
|     } | ||||
|   } | ||||
|   // Write GPIO to enable input mode | ||||
|   this->write_gpio_modes_(); | ||||
| } | ||||
|  | ||||
| void PI4IOE5V6408Component::loop() { this->reset_pin_cache_(); } | ||||
|  | ||||
| bool PI4IOE5V6408Component::read_gpio_outputs_() { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   uint8_t data; | ||||
|   if (!this->read_byte(PI4IOE5V6408_REGISTER_OUT_SET, &data)) { | ||||
|     this->status_set_warning("Failed to read output register"); | ||||
|     return false; | ||||
|   } | ||||
|   this->output_mask_ = data; | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool PI4IOE5V6408Component::read_gpio_modes_() { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   uint8_t data; | ||||
|   if (!this->read_byte(PI4IOE5V6408_REGISTER_IO_DIR, &data)) { | ||||
|     this->status_set_warning("Failed to read GPIO modes"); | ||||
|     return false; | ||||
|   } | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||
|   ESP_LOGV(TAG, "Read GPIO modes: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(data)); | ||||
| #endif | ||||
|   this->mode_mask_ = data; | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool PI4IOE5V6408Component::digital_read_hw(uint8_t pin) { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   uint8_t data; | ||||
|   if (!this->read_byte(PI4IOE5V6408_REGISTER_IN_STATE, &data)) { | ||||
|     this->status_set_warning("Failed to read GPIO state"); | ||||
|     return false; | ||||
|   } | ||||
|   this->input_mask_ = data; | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void PI4IOE5V6408Component::digital_write_hw(uint8_t pin, bool value) { | ||||
|   if (this->is_failed()) | ||||
|     return; | ||||
|  | ||||
|   if (value) { | ||||
|     this->output_mask_ |= (1 << pin); | ||||
|   } else { | ||||
|     this->output_mask_ &= ~(1 << pin); | ||||
|   } | ||||
|   if (!this->write_byte(PI4IOE5V6408_REGISTER_OUT_SET, this->output_mask_)) { | ||||
|     this->status_set_warning("Failed to write output register"); | ||||
|     return; | ||||
|   } | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||
|   ESP_LOGV(TAG, "Wrote GPIO output: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(this->output_mask_)); | ||||
| #endif | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
|  | ||||
| bool PI4IOE5V6408Component::write_gpio_modes_() { | ||||
|   if (this->is_failed()) | ||||
|     return false; | ||||
|  | ||||
|   if (!this->write_byte(PI4IOE5V6408_REGISTER_IO_DIR, this->mode_mask_)) { | ||||
|     this->status_set_warning("Failed to write GPIO modes"); | ||||
|     return false; | ||||
|   } | ||||
|   if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_SELECT, this->pull_up_down_mask_)) { | ||||
|     this->status_set_warning("Failed to write GPIO pullup/pulldown"); | ||||
|     return false; | ||||
|   } | ||||
|   if (!this->write_byte(PI4IOE5V6408_REGISTER_PULL_ENABLE, this->pull_enable_mask_)) { | ||||
|     this->status_set_warning("Failed to write GPIO pull enable"); | ||||
|     return false; | ||||
|   } | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||
|   ESP_LOGV(TAG, | ||||
|            "Wrote GPIO modes: 0b" BYTE_TO_BINARY_PATTERN "\n" | ||||
|            "Wrote GPIO pullup/pulldown: 0b" BYTE_TO_BINARY_PATTERN "\n" | ||||
|            "Wrote GPIO pull enable: 0b" BYTE_TO_BINARY_PATTERN, | ||||
|            BYTE_TO_BINARY(this->mode_mask_), BYTE_TO_BINARY(this->pull_up_down_mask_), | ||||
|            BYTE_TO_BINARY(this->pull_enable_mask_)); | ||||
| #endif | ||||
|   this->status_clear_warning(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool PI4IOE5V6408Component::digital_read_cache(uint8_t pin) { return (this->input_mask_ & (1 << pin)); } | ||||
|  | ||||
| float PI4IOE5V6408Component::get_setup_priority() const { return setup_priority::IO; } | ||||
|  | ||||
| void PI4IOE5V6408GPIOPin::setup() { this->pin_mode(this->flags_); } | ||||
| void PI4IOE5V6408GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } | ||||
| bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } | ||||
| void PI4IOE5V6408GPIOPin::digital_write(bool value) { | ||||
|   this->parent_->digital_write(this->pin_, value != this->inverted_); | ||||
| } | ||||
| std::string PI4IOE5V6408GPIOPin::dump_summary() const { return str_sprintf("%u via PI4IOE5V6408", this->pin_); } | ||||
|  | ||||
| }  // namespace pi4ioe5v6408 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										70
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/pi4ioe5v6408/pi4ioe5v6408.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/gpio_expander/cached_gpio.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pi4ioe5v6408 { | ||||
| class PI4IOE5V6408Component : public Component, | ||||
|                               public i2c::I2CDevice, | ||||
|                               public gpio_expander::CachedGpioExpander<uint8_t, 8> { | ||||
|  public: | ||||
|   PI4IOE5V6408Component() = default; | ||||
|  | ||||
|   void setup() override; | ||||
|   void pin_mode(uint8_t pin, gpio::Flags flags); | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|  | ||||
|   /// Indicate if the component should reset the state during setup | ||||
|   void set_reset(bool reset) { this->reset_ = reset; } | ||||
|  | ||||
|  protected: | ||||
|   bool digital_read_hw(uint8_t pin) override; | ||||
|   bool digital_read_cache(uint8_t pin) override; | ||||
|   void digital_write_hw(uint8_t pin, bool value) override; | ||||
|  | ||||
|   /// Mask for the pin mode - 1 means output, 0 means input | ||||
|   uint8_t mode_mask_{0x00}; | ||||
|   /// The mask to write as output state - 1 means HIGH, 0 means LOW | ||||
|   uint8_t output_mask_{0x00}; | ||||
|   /// The state read in digital_read_hw - 1 means HIGH, 0 means LOW | ||||
|   uint8_t input_mask_{0x00}; | ||||
|   /// The mask to write as input buffer state - 1 means enabled, 0 means disabled | ||||
|   uint8_t pull_enable_mask_{0x00}; | ||||
|   /// The mask to write as pullup state - 1 means pullup, 0 means pulldown | ||||
|   uint8_t pull_up_down_mask_{0x00}; | ||||
|  | ||||
|   bool reset_{true}; | ||||
|  | ||||
|   bool read_gpio_modes_(); | ||||
|   bool write_gpio_modes_(); | ||||
|   bool read_gpio_outputs_(); | ||||
| }; | ||||
|  | ||||
| class PI4IOE5V6408GPIOPin : public GPIOPin, public Parented<PI4IOE5V6408Component> { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void pin_mode(gpio::Flags flags) override; | ||||
|   bool digital_read() override; | ||||
|   void digital_write(bool value) override; | ||||
|   std::string dump_summary() const override; | ||||
|  | ||||
|   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||
|   void set_inverted(bool inverted) { this->inverted_ = inverted; } | ||||
|   void set_flags(gpio::Flags flags) { this->flags_ = flags; } | ||||
|  | ||||
|   gpio::Flags get_flags() const override { return this->flags_; } | ||||
|  | ||||
|  protected: | ||||
|   uint8_t pin_; | ||||
|   bool inverted_; | ||||
|   gpio::Flags flags_; | ||||
| }; | ||||
|  | ||||
| }  // namespace pi4ioe5v6408 | ||||
| }  // namespace esphome | ||||
| @@ -197,6 +197,7 @@ async def add_entity_config(entity, config): | ||||
|     sorting_weight = config.get(CONF_SORTING_WEIGHT, 50) | ||||
|     sorting_group_hash = hash(config.get(CONF_SORTING_GROUP_ID)) | ||||
|  | ||||
|     cg.add_define("USE_WEBSERVER_SORTING") | ||||
|     cg.add( | ||||
|         web_server.add_entity_config( | ||||
|             entity, | ||||
| @@ -284,4 +285,5 @@ async def to_code(config): | ||||
|         cg.add_define("USE_WEBSERVER_LOCAL") | ||||
|  | ||||
|     if (sorting_group_config := config.get(CONF_SORTING_GROUPS)) is not None: | ||||
|         cg.add_define("USE_WEBSERVER_SORTING") | ||||
|         add_sorting_groups(var, sorting_group_config) | ||||
|   | ||||
| @@ -184,6 +184,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUp | ||||
|   std::string message = ws->get_config_json(); | ||||
|   source->try_send_nodefer(message.c_str(), "ping", millis(), 30000); | ||||
|  | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
|   for (auto &group : ws->sorting_groups_) { | ||||
|     message = json::build_json([group](JsonObject root) { | ||||
|       root["name"] = group.second.name; | ||||
| @@ -193,6 +194,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUp | ||||
|     // up to 31 groups should be able to be queued initially without defer | ||||
|     source->try_send_nodefer(message.c_str(), "sorting_group"); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   source->entities_iterator_.begin(ws->include_internal_); | ||||
|  | ||||
| @@ -413,12 +415,7 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail | ||||
|     } | ||||
|     set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|       if (!obj->get_unit_of_measurement().empty()) | ||||
|         root["uom"] = obj->get_unit_of_measurement(); | ||||
|     } | ||||
| @@ -458,12 +455,7 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: | ||||
|   return json::build_json([this, obj, value, start_config](JsonObject root) { | ||||
|     set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -511,12 +503,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail | ||||
|     set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       root["assumed_state"] = obj->assumed_state(); | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -552,12 +539,7 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) | ||||
|   return json::build_json([this, obj, start_config](JsonObject root) { | ||||
|     set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -595,12 +577,7 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool | ||||
|     set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, | ||||
|                               start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -681,12 +658,7 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { | ||||
|     if (obj->get_traits().supports_oscillation()) | ||||
|       root["oscillation"] = obj->oscillating; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -802,12 +774,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi | ||||
|       for (auto const &option : obj->get_effects()) { | ||||
|         opt.add(option->get_name()); | ||||
|       } | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -888,12 +855,7 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { | ||||
|     if (obj->get_traits().get_supports_tilt()) | ||||
|       root["tilt"] = obj->tilt; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -954,12 +916,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail | ||||
|       root["mode"] = (int) obj->traits.get_mode(); | ||||
|       if (!obj->traits.get_unit_of_measurement().empty()) | ||||
|         root["uom"] = obj->traits.get_unit_of_measurement(); | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|     if (std::isnan(value)) { | ||||
|       root["value"] = "\"NaN\""; | ||||
| @@ -1028,12 +985,7 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con | ||||
|     root["value"] = value; | ||||
|     root["state"] = value; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1091,12 +1043,7 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con | ||||
|     root["value"] = value; | ||||
|     root["state"] = value; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1155,12 +1102,7 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s | ||||
|     root["value"] = value; | ||||
|     root["state"] = value; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1221,12 +1163,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json | ||||
|     root["value"] = value; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       root["mode"] = (int) obj->traits.get_mode(); | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1282,12 +1219,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value | ||||
|       for (auto &option : obj->traits.get_options()) { | ||||
|         opt.add(option); | ||||
|       } | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1404,12 +1336,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf | ||||
|         for (auto const &custom_preset : traits.get_supported_custom_presets()) | ||||
|           opt.add(custom_preset); | ||||
|       } | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|  | ||||
|     bool has_state = false; | ||||
| @@ -1502,12 +1429,7 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet | ||||
|     set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, | ||||
|                               start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1579,12 +1501,7 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { | ||||
|     if (obj->get_traits().get_supports_position()) | ||||
|       root["position"] = obj->position; | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1652,12 +1569,7 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro | ||||
|     set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), | ||||
|                               PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); | ||||
|     if (start_config == DETAIL_ALL) { | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1705,12 +1617,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty | ||||
|         event_types.add(event_type); | ||||
|       } | ||||
|       root["device_class"] = obj->get_device_class(); | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -1774,12 +1681,7 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c | ||||
|       root["title"] = obj->update_info.title; | ||||
|       root["summary"] = obj->update_info.summary; | ||||
|       root["release_url"] = obj->update_info.release_url; | ||||
|       if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { | ||||
|         root["sorting_weight"] = this->sorting_entitys_[obj].weight; | ||||
|         if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) { | ||||
|           root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name; | ||||
|         } | ||||
|       } | ||||
|       this->add_sorting_info_(root, obj); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -2093,6 +1995,18 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { | ||||
|  | ||||
| bool WebServer::isRequestHandlerTrivial() const { return false; } | ||||
|  | ||||
| void WebServer::add_sorting_info_(JsonObject &root, EntityBase *entity) { | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
|   if (this->sorting_entitys_.find(entity) != this->sorting_entitys_.end()) { | ||||
|     root["sorting_weight"] = this->sorting_entitys_[entity].weight; | ||||
|     if (this->sorting_groups_.find(this->sorting_entitys_[entity].group_id) != this->sorting_groups_.end()) { | ||||
|       root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[entity].group_id].name; | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
| void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) { | ||||
|   this->sorting_entitys_[entity] = SortingComponents{weight, group}; | ||||
| } | ||||
| @@ -2100,6 +2014,7 @@ void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t gro | ||||
| void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) { | ||||
|   this->sorting_groups_[group_id] = SortingGroup{group_name, weight}; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void WebServer::schedule_(std::function<void()> &&f) { | ||||
| #ifdef USE_ESP32 | ||||
|   | ||||
| @@ -46,6 +46,7 @@ struct UrlMatch { | ||||
|   bool valid;          ///< Whether this match is valid | ||||
| }; | ||||
|  | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
| struct SortingComponents { | ||||
|   float weight; | ||||
|   uint64_t group_id; | ||||
| @@ -55,6 +56,7 @@ struct SortingGroup { | ||||
|   std::string name; | ||||
|   float weight; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; | ||||
|  | ||||
| @@ -474,14 +476,18 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|   /// This web handle is not trivial. | ||||
|   bool isRequestHandlerTrivial() const override;  // NOLINT(readability-identifier-naming) | ||||
|  | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
|   void add_entity_config(EntityBase *entity, float weight, uint64_t group); | ||||
|   void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight); | ||||
|  | ||||
|   std::map<EntityBase *, SortingComponents> sorting_entitys_; | ||||
|   std::map<uint64_t, SortingGroup> sorting_groups_; | ||||
| #endif | ||||
|  | ||||
|   bool include_internal_{false}; | ||||
|  | ||||
|  protected: | ||||
|   void add_sorting_info_(JsonObject &root, EntityBase *entity); | ||||
|   void schedule_(std::function<void()> &&f); | ||||
|   web_server_base::WebServerBase *base_; | ||||
| #ifdef USE_ARDUINO | ||||
|   | ||||
| @@ -530,6 +530,7 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * | ||||
|   std::string message = ws->get_config_json(); | ||||
|   this->try_send_nodefer(message.c_str(), "ping", millis(), 30000); | ||||
|  | ||||
| #ifdef USE_WEBSERVER_SORTING | ||||
|   for (auto &group : ws->sorting_groups_) { | ||||
|     message = json::build_json([group](JsonObject root) { | ||||
|       root["name"] = group.second.name; | ||||
| @@ -540,6 +541,7 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * | ||||
|     // since the only thing in the send buffer at this point is the initial ping/config | ||||
|     this->try_send_nodefer(message.c_str(), "sorting_group"); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   this->entities_iterator_->begin(ws->include_internal_); | ||||
|  | ||||
|   | ||||
| @@ -152,6 +152,7 @@ | ||||
| #define USE_WEBSERVER | ||||
| #define USE_WEBSERVER_OTA | ||||
| #define USE_WEBSERVER_PORT 80  // NOLINT | ||||
| #define USE_WEBSERVER_SORTING | ||||
| #define USE_WIFI_11KV_SUPPORT | ||||
|  | ||||
| #ifdef USE_ARDUINO | ||||
|   | ||||
| @@ -1034,7 +1034,7 @@ SOURCE_BOTH = 0 | ||||
| SOURCE_SERVER = 1 | ||||
| SOURCE_CLIENT = 2 | ||||
|  | ||||
| RECEIVE_CASES: dict[int, str] = {} | ||||
| RECEIVE_CASES: dict[int, tuple[str, str | None]] = {} | ||||
|  | ||||
| ifdefs: dict[str, str] = {} | ||||
|  | ||||
| @@ -1208,8 +1208,6 @@ def build_service_message_type( | ||||
|         func = f"on_{snake}" | ||||
|         hout += f"virtual void {func}(const {mt.name} &value){{}};\n" | ||||
|         case = "" | ||||
|         if ifdef is not None: | ||||
|             case += f"#ifdef {ifdef}\n" | ||||
|         case += f"{mt.name} msg;\n" | ||||
|         case += "msg.decode(msg_data, msg_size);\n" | ||||
|         if log: | ||||
| @@ -1217,10 +1215,9 @@ def build_service_message_type( | ||||
|             case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' | ||||
|             case += "#endif\n" | ||||
|         case += f"this->{func}(msg);\n" | ||||
|         if ifdef is not None: | ||||
|             case += "#endif\n" | ||||
|         case += "break;" | ||||
|         RECEIVE_CASES[id_] = case | ||||
|         # Store the ifdef with the case for later use | ||||
|         RECEIVE_CASES[id_] = (case, ifdef) | ||||
|  | ||||
|         # Only close ifdef if we opened it | ||||
|         if ifdef is not None: | ||||
| @@ -1379,18 +1376,21 @@ def main() -> None: | ||||
|     cases = list(RECEIVE_CASES.items()) | ||||
|     cases.sort() | ||||
|     hpp += " protected:\n" | ||||
|     hpp += "  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" | ||||
|     out = f"bool {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" | ||||
|     hpp += "  void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;\n" | ||||
|     out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {{\n" | ||||
|     out += "  switch (msg_type) {\n" | ||||
|     for i, case in cases: | ||||
|         c = f"case {i}: {{\n" | ||||
|         c += indent(case) + "\n" | ||||
|         c += "}" | ||||
|         out += indent(c, "    ") + "\n" | ||||
|     for i, (case, ifdef) in cases: | ||||
|         if ifdef is not None: | ||||
|             out += f"#ifdef {ifdef}\n" | ||||
|         c = f"    case {i}: {{\n" | ||||
|         c += indent(case, "      ") + "\n" | ||||
|         c += "    }" | ||||
|         out += c + "\n" | ||||
|         if ifdef is not None: | ||||
|             out += "#endif\n" | ||||
|     out += "    default:\n" | ||||
|     out += "      return false;\n" | ||||
|     out += "      break;\n" | ||||
|     out += "  }\n" | ||||
|     out += "  return true;\n" | ||||
|     out += "}\n" | ||||
|     cpp += out | ||||
|     hpp += "};\n" | ||||
|   | ||||
							
								
								
									
										22
									
								
								tests/components/pi4ioe5v6408/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/components/pi4ioe5v6408/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| i2c: | ||||
|   id: i2c_pi4ioe5v6408 | ||||
|   sda: ${i2c_sda} | ||||
|   scl: ${i2c_scl} | ||||
|  | ||||
| pi4ioe5v6408: | ||||
|   id: pi4ioe1 | ||||
|   address: 0x44 | ||||
|  | ||||
| switch: | ||||
|   - platform: gpio | ||||
|     id: switch1 | ||||
|     pin: | ||||
|       pi4ioe5v6408: pi4ioe1 | ||||
|       number: 0 | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: gpio | ||||
|     id: sensor1 | ||||
|     pin: | ||||
|       pi4ioe5v6408: pi4ioe1 | ||||
|       number: 1 | ||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   i2c_sda: GPIO21 | ||||
|   i2c_scl: GPIO22 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   i2c_sda: GPIO21 | ||||
|   i2c_scl: GPIO22 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/pi4ioe5v6408/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/pi4ioe5v6408/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   i2c_sda: GPIO4 | ||||
|   i2c_scl: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user