mirror of
https://github.com/esphome/esphome.git
synced 2025-11-20 16:55:49 +00:00
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.11.0b4
|
PROJECT_NUMBER = 2025.11.0b5
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ def customise_schema(config):
|
|||||||
"""
|
"""
|
||||||
config = cv.Schema(
|
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,
|
extra=cv.ALLOW_EXTRA,
|
||||||
)(config)
|
)(config)
|
||||||
|
|||||||
@@ -32,11 +32,15 @@ class SpectraE6(EpaperModel):
|
|||||||
|
|
||||||
spectra_e6 = SpectraE6("spectra-e6")
|
spectra_e6 = SpectraE6("spectra-e6")
|
||||||
|
|
||||||
spectra_e6.extend(
|
spectra_e6_7p3 = spectra_e6.extend(
|
||||||
"Seeed-reTerminal-E1002",
|
"7.3in-Spectra-E6",
|
||||||
width=800,
|
width=800,
|
||||||
height=480,
|
height=480,
|
||||||
data_rate="20MHz",
|
data_rate="20MHz",
|
||||||
|
)
|
||||||
|
|
||||||
|
spectra_e6_7p3.extend(
|
||||||
|
"Seeed-reTerminal-E1002",
|
||||||
cs_pin=10,
|
cs_pin=10,
|
||||||
dc_pin=11,
|
dc_pin=11,
|
||||||
reset_pin=12,
|
reset_pin=12,
|
||||||
|
|||||||
@@ -66,10 +66,14 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &su
|
|||||||
: substitutions_(substitutions) {}
|
: substitutions_(substitutions) {}
|
||||||
|
|
||||||
optional<std::string> SubstituteFilter::new_value(std::string value) {
|
optional<std::string> SubstituteFilter::new_value(std::string value) {
|
||||||
std::size_t pos;
|
|
||||||
for (const auto &sub : this->substitutions_) {
|
for (const auto &sub : this->substitutions_) {
|
||||||
while ((pos = value.find(sub.from)) != std::string::npos)
|
std::size_t pos = 0;
|
||||||
|
while ((pos = value.find(sub.from, pos)) != std::string::npos) {
|
||||||
value.replace(pos, sub.from.size(), sub.to);
|
value.replace(pos, sub.from.size(), sub.to);
|
||||||
|
// Advance past the replacement to avoid infinite loop when
|
||||||
|
// the replacement contains the search pattern (e.g., f -> foo)
|
||||||
|
pos += sub.to.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // 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() {
|
void AsyncWebServer::end() {
|
||||||
if (this->server_) {
|
if (this->server_) {
|
||||||
httpd_stop(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; };
|
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)
|
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
|
||||||
config.lru_purge_enable = this->lru_purge_enable_;
|
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) {
|
if (httpd_start(&this->server_, &config) == ESP_OK) {
|
||||||
const httpd_uri_t handler_get = {
|
const httpd_uri_t handler_get = {
|
||||||
.uri = "",
|
.uri = "",
|
||||||
@@ -505,17 +530,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
|
|||||||
void AsyncEventSourceResponse::destroy(void *ptr) {
|
void AsyncEventSourceResponse::destroy(void *ptr) {
|
||||||
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
|
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
|
||||||
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
|
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
|
||||||
|
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
||||||
if (fd > 0) {
|
// Mark as dead - will be cleaned up in the main loop
|
||||||
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
// Note: We don't delete or remove from set here to avoid race conditions
|
||||||
// Immediately shut down the socket to prevent lwIP from delivering more data
|
// httpd will call our custom close_fn (safe_close_with_shutdown) which handles
|
||||||
// This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack
|
// shutdown() before close() to prevent lwIP race conditions
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper for allowing only unique entries in the queue
|
// helper for allowing only unique entries in the queue
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ class AsyncWebServer {
|
|||||||
static esp_err_t request_handler(httpd_req_t *r);
|
static esp_err_t request_handler(httpd_req_t *r);
|
||||||
static esp_err_t request_post_handler(httpd_req_t *r);
|
static esp_err_t request_post_handler(httpd_req_t *r);
|
||||||
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
||||||
|
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
|
||||||
#ifdef USE_WEBSERVER_OTA
|
#ifdef USE_WEBSERVER_OTA
|
||||||
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
|
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -870,7 +870,13 @@ bssid_t WiFiComponent::wifi_bssid() {
|
|||||||
return bssid;
|
return bssid;
|
||||||
}
|
}
|
||||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||||
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
|
int8_t WiFiComponent::wifi_rssi() {
|
||||||
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
|
return WIFI_RSSI_DISCONNECTED;
|
||||||
|
int8_t rssi = WiFi.RSSI();
|
||||||
|
// Values >= 31 are error codes per NONOS SDK API, not valid RSSI readings
|
||||||
|
return rssi >= 31 ? WIFI_RSSI_DISCONNECTED : rssi;
|
||||||
|
}
|
||||||
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
|
||||||
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
|
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
|
||||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
|
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.11.0b4"
|
__version__ = "2025.11.0b5"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ wifi:
|
|||||||
password: PASSWORD123
|
password: PASSWORD123
|
||||||
|
|
||||||
time:
|
time:
|
||||||
platform: sntp
|
- platform: sntp
|
||||||
id: time_id
|
id: sntp_time
|
||||||
|
|
||||||
text:
|
text:
|
||||||
- id: lvgl_text
|
- id: lvgl_text
|
||||||
|
|||||||
@@ -478,19 +478,19 @@ lvgl:
|
|||||||
id: hello_label
|
id: hello_label
|
||||||
text:
|
text:
|
||||||
time_format: "%c"
|
time_format: "%c"
|
||||||
time: time_id
|
time: sntp_time
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: hello_label
|
id: hello_label
|
||||||
text:
|
text:
|
||||||
time_format: "%c"
|
time_format: "%c"
|
||||||
time: !lambda return id(time_id).now();
|
time: !lambda return id(sntp_time).now();
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: hello_label
|
id: hello_label
|
||||||
text:
|
text:
|
||||||
time_format: "%c"
|
time_format: "%c"
|
||||||
time: !lambda |-
|
time: !lambda |-
|
||||||
ESP_LOGD("label", "multi-line lambda");
|
ESP_LOGD("label", "multi-line lambda");
|
||||||
return id(time_id).now();
|
return id(sntp_time).now();
|
||||||
on_value:
|
on_value:
|
||||||
logger.log:
|
logger.log:
|
||||||
format: "state now %d"
|
format: "state now %d"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ wifi:
|
|||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: sntp
|
- platform: sntp
|
||||||
|
id: sntp_time
|
||||||
|
|
||||||
mqtt:
|
mqtt:
|
||||||
broker: "192.168.178.84"
|
broker: "192.168.178.84"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ wifi:
|
|||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: sntp
|
- platform: sntp
|
||||||
|
id: sntp_time
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: uptime
|
- platform: uptime
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ wifi:
|
|||||||
|
|
||||||
time:
|
time:
|
||||||
- platform: sntp
|
- platform: sntp
|
||||||
|
id: sntp_time
|
||||||
|
|
||||||
wireguard:
|
wireguard:
|
||||||
|
time_id: sntp_time
|
||||||
address: 172.16.34.100
|
address: 172.16.34.100
|
||||||
netmask: 255.255.255.0
|
netmask: 255.255.255.0
|
||||||
# NEVER use the following keys for your VPN -- they are now public!
|
# NEVER use the following keys for your VPN -- they are now public!
|
||||||
|
|||||||
Reference in New Issue
Block a user