1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-20 08:46:01 +00:00

Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2025-11-19 16:42:04 -06:00
7 changed files with 46 additions and 22 deletions

View File

@@ -66,7 +66,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in
static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%llu", value);
snprintf(buffer, 64, "%" PRIu64, value);
append_with_newline(out, buffer);
}

View File

@@ -102,7 +102,7 @@ def customise_schema(config):
"""
config = cv.Schema(
{
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"),
},
extra=cv.ALLOW_EXTRA,
)(config)

View File

@@ -32,11 +32,15 @@ class SpectraE6(EpaperModel):
spectra_e6 = SpectraE6("spectra-e6")
spectra_e6.extend(
"Seeed-reTerminal-E1002",
spectra_e6_7p3 = spectra_e6.extend(
"7.3in-Spectra-E6",
width=800,
height=480,
data_rate="20MHz",
)
spectra_e6_7p3.extend(
"Seeed-reTerminal-E1002",
cs_pin=10,
dc_pin=11,
reset_pin=12,

View File

@@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_
}
} // namespace
void AsyncWebServer::safe_close_with_shutdown(httpd_handle_t hd, int sockfd) {
// CRITICAL: Shut down receive BEFORE closing to prevent lwIP race conditions
//
// The race condition occurs because close() initiates lwIP teardown while
// the TCP/IP thread can still receive packets, causing assertions when
// recv_tcp() sees partially-torn-down state.
//
// By shutting down receive first, we tell lwIP to stop accepting new data BEFORE
// the teardown begins, eliminating the race window. We only shutdown RD (not RDWR)
// to allow the FIN packet to be sent cleanly during close().
//
// Note: This function may be called with an already-closed socket if the network
// stack closed it. In that case, shutdown() will fail but close() is safe to call.
//
// See: https://github.com/esphome/esphome-webserver/issues/163
// Attempt shutdown - ignore errors as socket may already be closed
shutdown(sockfd, SHUT_RD);
// Always close - safe even if socket is already closed by network stack
close(sockfd);
}
void AsyncWebServer::end() {
if (this->server_) {
httpd_stop(this->server_);
@@ -115,6 +138,8 @@ void AsyncWebServer::begin() {
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
config.lru_purge_enable = this->lru_purge_enable_;
// Use custom close function that shuts down before closing to prevent lwIP race conditions
config.close_fn = AsyncWebServer::safe_close_with_shutdown;
if (httpd_start(&this->server_, &config) == ESP_OK) {
const httpd_uri_t handler_get = {
.uri = "",
@@ -506,17 +531,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
void AsyncEventSourceResponse::destroy(void *ptr) {
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
if (fd > 0) {
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
// Immediately shut down the socket to prevent lwIP from delivering more data
// This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack
// tries to deliver queued data after the session is marked as dead
// See: https://github.com/esphome/esphome/issues/11936
shutdown(fd, SHUT_RDWR);
// Note: We don't close() the socket - httpd owns it and will close it
}
// Session will be cleaned up in the main loop to avoid race conditions
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
// Mark as dead - will be cleaned up in the main loop
// Note: We don't delete or remove from set here to avoid race conditions
// httpd will call our custom close_fn (safe_close_with_shutdown) which handles
// shutdown() before close() to prevent lwIP race conditions
}
// helper for allowing only unique entries in the queue

View File

@@ -209,6 +209,7 @@ class AsyncWebServer {
static esp_err_t request_handler(httpd_req_t *r);
static esp_err_t request_post_handler(httpd_req_t *r);
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
#ifdef USE_WEBSERVER_OTA
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
#endif

View File

@@ -1,7 +1,7 @@
pylint==4.0.3
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
ruff==0.14.5 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.21.1 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
pre-commit
# Unit tests

View File

@@ -462,7 +462,7 @@ class Int64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
o += "out.append(buffer);"
return o
@@ -482,7 +482,7 @@ class UInt64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n'
o += "out.append(buffer);"
return o
@@ -522,7 +522,7 @@ class Fixed64Type(TypeInfo):
wire_type = WireType.FIXED64 # Uses wire type 1
def dump(self, name: str) -> str:
o = f'snprintf(buffer, sizeof(buffer), "%llu", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRIu64, {name});\n'
o += "out.append(buffer);"
return o
@@ -1106,7 +1106,7 @@ class SFixed64Type(TypeInfo):
wire_type = WireType.FIXED64 # Uses wire type 1
def dump(self, name: str) -> str:
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
o += "out.append(buffer);"
return o
@@ -1150,7 +1150,7 @@ class SInt64Type(TypeInfo):
wire_type = WireType.VARINT # Uses wire type 0
def dump(self, name: str) -> str:
o = f'snprintf(buffer, sizeof(buffer), "%lld", {name});\n'
o = f'snprintf(buffer, sizeof(buffer), "%" PRId64, {name});\n'
o += "out.append(buffer);"
return o
@@ -2546,7 +2546,7 @@ static void dump_field(std::string &out, const char *field_name, float value, in
static void dump_field(std::string &out, const char *field_name, uint64_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
snprintf(buffer, 64, "%llu", value);
snprintf(buffer, 64, "%" PRIu64, value);
append_with_newline(out, buffer);
}