mirror of
https://github.com/esphome/esphome.git
synced 2025-09-19 11:42:20 +01:00
Merge branch 'batch_ping_fallback' into integration
This commit is contained in:
@@ -65,10 +65,6 @@ uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_
|
|||||||
void APIConnection::start() {
|
void APIConnection::start() {
|
||||||
this->last_traffic_ = App.get_loop_component_start_time();
|
this->last_traffic_ = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Set next_ping_retry_ to prevent immediate ping
|
|
||||||
// This ensures the first ping happens after the keepalive period
|
|
||||||
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
|
|
||||||
|
|
||||||
APIError err = this->helper_->init();
|
APIError err = this->helper_->init();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
@@ -163,20 +159,15 @@ void APIConnection::loop() {
|
|||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
||||||
}
|
}
|
||||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
|
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
|
||||||
ESP_LOGVV(TAG, "Sending keepalive PING");
|
ESP_LOGVV(TAG, "Sending keepalive PING");
|
||||||
this->sent_ping_ = this->send_message(PingRequest());
|
this->sent_ping_ = this->send_message(PingRequest());
|
||||||
if (!this->sent_ping_) {
|
if (!this->sent_ping_) {
|
||||||
this->next_ping_retry_ = now + PING_RETRY_INTERVAL;
|
// If we can't send the ping request directly (tx_buffer full),
|
||||||
this->ping_retries_++;
|
// schedule it at the front of the batch so it will be sent with priority
|
||||||
if (this->ping_retries_ >= MAX_PING_RETRIES) {
|
ESP_LOGVV(TAG, "Failed to send ping directly, scheduling at front of batch");
|
||||||
on_fatal_error();
|
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
|
||||||
ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_);
|
this->sent_ping_ = true; // Mark as sent to avoid scheduling multiple pings
|
||||||
} else if (this->ping_retries_ >= 10) {
|
|
||||||
ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1752,6 +1743,11 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
items.emplace_back(entity, std::move(creator), message_type);
|
items.emplace_back(entity, std::move(creator), message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
||||||
|
// Insert at front for high priority messages (no deduplication check)
|
||||||
|
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
|
||||||
|
}
|
||||||
|
|
||||||
bool APIConnection::schedule_batch_() {
|
bool APIConnection::schedule_batch_() {
|
||||||
if (!this->deferred_batch_.batch_scheduled) {
|
if (!this->deferred_batch_.batch_scheduled) {
|
||||||
this->deferred_batch_.batch_scheduled = true;
|
this->deferred_batch_.batch_scheduled = true;
|
||||||
@@ -1933,6 +1929,12 @@ uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConne
|
|||||||
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
|
bool is_single) {
|
||||||
|
PingRequest req;
|
||||||
|
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
||||||
// Use generated ESTIMATED_SIZE constants from each message type
|
// Use generated ESTIMATED_SIZE constants from each message type
|
||||||
switch (message_type) {
|
switch (message_type) {
|
||||||
|
@@ -185,7 +185,6 @@ class APIConnection : public APIServerConnection {
|
|||||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||||
void on_ping_response(const PingResponse &value) override {
|
void on_ping_response(const PingResponse &value) override {
|
||||||
// we initiated ping
|
// we initiated ping
|
||||||
this->ping_retries_ = 0;
|
|
||||||
this->sent_ping_ = false;
|
this->sent_ping_ = false;
|
||||||
}
|
}
|
||||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||||
@@ -441,13 +440,16 @@ class APIConnection : public APIServerConnection {
|
|||||||
// Helper function to get estimated message size for buffer pre-allocation
|
// Helper function to get estimated message size for buffer pre-allocation
|
||||||
static uint16_t get_estimated_message_size(uint16_t message_type);
|
static uint16_t get_estimated_message_size(uint16_t message_type);
|
||||||
|
|
||||||
|
// Batch message method for ping requests
|
||||||
|
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)
|
// Pointers first (4 bytes each, naturally aligned)
|
||||||
std::unique_ptr<APIFrameHelper> helper_;
|
std::unique_ptr<APIFrameHelper> helper_;
|
||||||
APIServer *parent_;
|
APIServer *parent_;
|
||||||
|
|
||||||
// 4-byte aligned types
|
// 4-byte aligned types
|
||||||
uint32_t last_traffic_;
|
uint32_t last_traffic_;
|
||||||
uint32_t next_ping_retry_{0};
|
|
||||||
int state_subs_at_ = -1;
|
int state_subs_at_ = -1;
|
||||||
|
|
||||||
// Strings (12 bytes each on 32-bit)
|
// Strings (12 bytes each on 32-bit)
|
||||||
@@ -470,8 +472,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool sent_ping_{false};
|
bool sent_ping_{false};
|
||||||
bool service_call_subscription_{false};
|
bool service_call_subscription_{false};
|
||||||
bool next_close_ = false;
|
bool next_close_ = false;
|
||||||
uint8_t ping_retries_{0};
|
// 7 bytes used, 1 byte padding
|
||||||
// 8 bytes used, no padding needed
|
|
||||||
|
|
||||||
// Larger objects at the end
|
// Larger objects at the end
|
||||||
InitialStateIterator initial_state_iterator_;
|
InitialStateIterator initial_state_iterator_;
|
||||||
@@ -592,6 +593,8 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
|
// Add item to the front of the batch (for high priority messages like ping)
|
||||||
|
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
void clear() {
|
void clear() {
|
||||||
items.clear();
|
items.clear();
|
||||||
batch_scheduled = false;
|
batch_scheduled = false;
|
||||||
@@ -631,6 +634,12 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||||
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to schedule a high priority message at the front of the batch
|
||||||
|
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
||||||
|
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
|
||||||
|
return this->schedule_batch_();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
|
@@ -466,7 +466,7 @@ LVGL_SCHEMA = cv.All(
|
|||||||
): lvalid.lv_color,
|
): lvalid.lv_color,
|
||||||
cv.Optional(df.CONF_THEME): cv.Schema(
|
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(name): obj_schema(w)
|
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||||
for name, w in WIDGET_TYPES.items()
|
for name, w in WIDGET_TYPES.items()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@@ -21,7 +21,7 @@ from esphome.core.config import StartupTrigger
|
|||||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||||
|
|
||||||
from . import defines as df, lv_validation as lvalid
|
from . import defines as df, lv_validation as lvalid
|
||||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID
|
||||||
from .helpers import add_lv_use, requires_component, validate_printf
|
from .helpers import add_lv_use, requires_component, validate_printf
|
||||||
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
||||||
from .lvcode import LvglComponent, lv_event_t_ptr
|
from .lvcode import LvglComponent, lv_event_t_ptr
|
||||||
@@ -349,7 +349,60 @@ def obj_schema(widget_type: WidgetType):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_grid_layout(config):
|
||||||
|
layout = config[df.CONF_LAYOUT]
|
||||||
|
rows = len(layout[df.CONF_GRID_ROWS])
|
||||||
|
columns = len(layout[df.CONF_GRID_COLUMNS])
|
||||||
|
used_cells = [[None] * columns for _ in range(rows)]
|
||||||
|
for index, widget in enumerate(config[df.CONF_WIDGETS]):
|
||||||
|
_, w = next(iter(widget.items()))
|
||||||
|
if (df.CONF_GRID_CELL_COLUMN_POS in w) != (df.CONF_GRID_CELL_ROW_POS in w):
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Both row and column positions must be specified, or both omitted",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
if df.CONF_GRID_CELL_ROW_POS in w:
|
||||||
|
row = w[df.CONF_GRID_CELL_ROW_POS]
|
||||||
|
column = w[df.CONF_GRID_CELL_COLUMN_POS]
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
row, column = next(
|
||||||
|
(r_idx, c_idx)
|
||||||
|
for r_idx, row in enumerate(used_cells)
|
||||||
|
for c_idx, value in enumerate(row)
|
||||||
|
if value is None
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
"No free cells available in grid layout", [df.CONF_WIDGETS, index]
|
||||||
|
)
|
||||||
|
w[df.CONF_GRID_CELL_ROW_POS] = row
|
||||||
|
w[df.CONF_GRID_CELL_COLUMN_POS] = column
|
||||||
|
|
||||||
|
for i in range(w[df.CONF_GRID_CELL_ROW_SPAN]):
|
||||||
|
for j in range(w[df.CONF_GRID_CELL_COLUMN_SPAN]):
|
||||||
|
if row + i >= rows or column + j >= columns:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Cell at {row}/{column} span {w[df.CONF_GRID_CELL_ROW_SPAN]}x{w[df.CONF_GRID_CELL_COLUMN_SPAN]} "
|
||||||
|
f"exceeds grid size {rows}x{columns}",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
if used_cells[row + i][column + j] is not None:
|
||||||
|
# pylint: disable=raise-missing-from
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}",
|
||||||
|
[df.CONF_WIDGETS, index],
|
||||||
|
)
|
||||||
|
used_cells[row + i][column + j] = index
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
LAYOUT_SCHEMAS = {}
|
LAYOUT_SCHEMAS = {}
|
||||||
|
LAYOUT_VALIDATORS = {TYPE_GRID: _validate_grid_layout}
|
||||||
|
|
||||||
ALIGN_TO_SCHEMA = {
|
ALIGN_TO_SCHEMA = {
|
||||||
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
||||||
@@ -402,8 +455,8 @@ LAYOUT_SCHEMA = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GRID_CELL_SCHEMA = {
|
GRID_CELL_SCHEMA = {
|
||||||
cv.Required(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||||
cv.Required(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
||||||
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||||
@@ -474,7 +527,10 @@ def container_validator(schema, widget_type: WidgetType):
|
|||||||
result = result.extend(
|
result = result.extend(
|
||||||
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
|
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
|
||||||
)
|
)
|
||||||
return result(value)
|
value = result(value)
|
||||||
|
if layout_validator := LAYOUT_VALIDATORS.get(ltype):
|
||||||
|
value = layout_validator(value)
|
||||||
|
return value
|
||||||
|
|
||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
@@ -839,9 +839,7 @@ lvgl:
|
|||||||
styles: bdr_style
|
styles: bdr_style
|
||||||
grid_cell_x_align: center
|
grid_cell_x_align: center
|
||||||
grid_cell_y_align: stretch
|
grid_cell_y_align: stretch
|
||||||
grid_cell_row_pos: 0
|
grid_cell_column_span: 2
|
||||||
grid_cell_column_pos: 1
|
|
||||||
grid_cell_column_span: 1
|
|
||||||
text: "Grid cell 0/1"
|
text: "Grid cell 0/1"
|
||||||
- label:
|
- label:
|
||||||
grid_cell_x_align: end
|
grid_cell_x_align: end
|
||||||
|
Reference in New Issue
Block a user