Compare commits

..
35 changed files with 252 additions and 500 deletions

No files matched your search

+12 -9
View File
@@ -7,6 +7,7 @@ from esphome.components.network import (
add_use_address,
get_network_priority,
get_priority_interfaces_from_full_config,
ip_address_literal,
require_ipv4,
)
from esphome.config_helpers import (
@@ -238,6 +239,7 @@ MANUAL_IP_SCHEMA = cv.Schema(
)
EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component)
ManualIP = ethernet_ns.struct("ManualIP")
def _is_framework_spi_polling_mode_supported() -> bool:
@@ -623,10 +625,15 @@ def _final_validate_spi(config: ConfigType) -> None:
)
def _manual_ip_table(config: ConfigType) -> list[int]:
"""Pack manual_ip in the ETHERNET_MANUAL_IP layout."""
keys = (CONF_STATIC_IP, CONF_GATEWAY, CONF_SUBNET, CONF_DNS1, CONF_DNS2)
return [octet for key in keys for octet in config[key].packed]
def manual_ip(config: ConfigType) -> cg.StructInitializer:
return cg.StructInitializer(
ManualIP,
("static_ip", ip_address_literal(config[CONF_STATIC_IP])),
("gateway", ip_address_literal(config[CONF_GATEWAY])),
("subnet", ip_address_literal(config[CONF_SUBNET])),
("dns1", ip_address_literal(config[CONF_DNS1])),
("dns2", ip_address_literal(config[CONF_DNS2])),
)
def phy_register(address: int, value: int, page: int) -> cg.StructInitializer:
@@ -678,11 +685,7 @@ async def to_code(config: ConfigType) -> None:
if CONF_MANUAL_IP in config:
cg.add_define("USE_ETHERNET_MANUAL_IP")
cg.extern_progmem_array(
"esphome::ethernet::ETHERNET_MANUAL_IP",
cg.uint8,
_manual_ip_table(config[CONF_MANUAL_IP]),
)
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
# Add compile-time define for PHY types with specific code
if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]):
@@ -10,15 +10,6 @@ EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-
EthernetComponent::EthernetComponent() { global_eth_component = this; }
#ifdef USE_ETHERNET_MANUAL_IP
ManualIP load_manual_ip() {
uint8_t b[ETHERNET_MANUAL_IP_SIZE];
progmem_memcpy(b, ETHERNET_MANUAL_IP, sizeof(b));
auto ip = [&b](size_t i) { return network::IPAddress(b[i], b[i + 1], b[i + 2], b[i + 3]); };
return ManualIP{ip(0), ip(4), ip(8), ip(12), ip(16)};
}
#endif
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void EthernetComponent::notify_ip_state_listeners_() {
auto ips = this->get_ip_addresses();
@@ -103,14 +103,6 @@ struct ManualIP {
network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default.
};
#ifdef USE_ETHERNET_MANUAL_IP
/// The configured static IP, gateway, subnet, dns1 and dns2 as 4 octets each, generated by codegen
/// (components/ethernet/__init__.py) and kept in flash.
static constexpr size_t ETHERNET_MANUAL_IP_SIZE = 5 * 4;
extern const uint8_t ETHERNET_MANUAL_IP[ETHERNET_MANUAL_IP_SIZE] PROGMEM;
ManualIP load_manual_ip();
#endif
struct PHYRegister {
uint32_t address;
uint32_t value;
@@ -159,6 +151,9 @@ class EthernetComponent final : public Component {
#endif
void set_type(EthernetType type) { this->type_ = type; }
#ifdef USE_ETHERNET_MANUAL_IP
void set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
#endif
void set_fixed_mac(const std::array<uint8_t, MAC_ADDRESS_SIZE> &mac) { this->fixed_mac_ = mac; }
network::IPAddresses get_ip_addresses();
@@ -338,6 +333,9 @@ class EthernetComponent final : public Component {
#endif // USE_RP2
// Common members
#ifdef USE_ETHERNET_MANUAL_IP
optional<ManualIP> manual_ip_{};
#endif
uint32_t connect_begin_;
// Group all uint8_t types together (enums and bools)
@@ -763,7 +763,9 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
event_name = "ETH connected";
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
global_eth_component->notify_ip_state_listeners_();
if (global_eth_component->manual_ip_.has_value()) {
global_eth_component->notify_ip_state_listeners_();
}
#endif
#if USE_NETWORK_IPV6
// Start SLAAC on link-up, not after the DHCPv4 lease. This also restores the
@@ -887,13 +889,19 @@ void EthernetComponent::start_connect_() {
ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err));
}
esp_netif_ip_info_t info{};
esp_netif_ip_info_t info;
#ifdef USE_ETHERNET_MANUAL_IP
const ManualIP manual_ip = load_manual_ip();
info.ip = manual_ip.static_ip;
info.gw = manual_ip.gateway;
info.netmask = manual_ip.subnet;
if (this->manual_ip_.has_value()) {
info.ip = this->manual_ip_->static_ip;
info.gw = this->manual_ip_->gateway;
info.netmask = this->manual_ip_->subnet;
} else
#endif
{
info.ip.addr = 0;
info.gw.addr = 0;
info.netmask.addr = 0;
}
esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT;
@@ -911,32 +919,35 @@ void EthernetComponent::start_connect_() {
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
#ifdef USE_ETHERNET_MANUAL_IP
// Set DNS through esp_netif so the servers are stored in the netif's own
// dns[] array; raw dns_setserver() would be lost when the default-route
// arbitration re-applies the default netif's DNS.
// Log-only on failure: the link still has a working IP/gateway, so degraded
// name resolution does not justify marking the whole component failed.
esp_netif_dns_info_t dns{};
if (manual_ip.dns1.is_set()) {
dns.ip = manual_ip.dns1;
err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_MAIN, &dns);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Set main DNS failed: %s", esp_err_to_name(err));
if (this->manual_ip_.has_value()) {
// Set DNS through esp_netif so the servers are stored in the netif's own
// dns[] array; raw dns_setserver() would be lost when the default-route
// arbitration re-applies the default netif's DNS.
// Log-only on failure: the link still has a working IP/gateway, so degraded
// name resolution does not justify marking the whole component failed.
esp_netif_dns_info_t dns{};
if (this->manual_ip_->dns1.is_set()) {
dns.ip = this->manual_ip_->dns1;
err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_MAIN, &dns);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Set main DNS failed: %s", esp_err_to_name(err));
}
}
}
if (manual_ip.dns2.is_set()) {
dns.ip = manual_ip.dns2;
err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_BACKUP, &dns);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Set backup DNS failed: %s", esp_err_to_name(err));
if (this->manual_ip_->dns2.is_set()) {
dns.ip = this->manual_ip_->dns2;
err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_BACKUP, &dns);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Set backup DNS failed: %s", esp_err_to_name(err));
}
}
}
#else
err = esp_netif_dhcpc_start(this->eth_netif_);
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
}
} else
#endif
{
err = esp_netif_dhcpc_start(this->eth_netif_);
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
}
}
#if USE_NETWORK_IPV6
// Attempt to create IPv6 link-local address
// We MUST attempt this here, not just in finish_connect_(), because with
@@ -56,12 +56,15 @@ void EthernetComponent::setup() {
// Set hostname before begin() so the LWIP netif gets it
this->eth_->hostname(App.get_name().c_str());
// Configure static IP (must be done before begin())
// Configure static IP if set (must be done before begin())
#ifdef USE_ETHERNET_MANUAL_IP
{
const ManualIP manual_ip = load_manual_ip();
this->eth_->config(IPAddress(manual_ip.static_ip), IPAddress(manual_ip.gateway), IPAddress(manual_ip.subnet),
IPAddress(manual_ip.dns1), IPAddress(manual_ip.dns2));
if (this->manual_ip_.has_value()) {
IPAddress ip(this->manual_ip_->static_ip);
IPAddress gateway(this->manual_ip_->gateway);
IPAddress subnet(this->manual_ip_->subnet);
IPAddress dns1(this->manual_ip_->dns1);
IPAddress dns2(this->manual_ip_->dns2);
this->eth_->config(ip, gateway, subnet, dns1, dns2);
}
#endif
@@ -287,17 +290,20 @@ void EthernetComponent::start_connect_() {
// Hostname is already set in setup() via LwipIntf::setHostname()
#ifdef USE_ETHERNET_MANUAL_IP
// Static IP was already configured before begin() in setup()
// Set DNS servers
const ManualIP manual_ip = load_manual_ip();
LwIPLock lock;
if (manual_ip.dns1.is_set()) {
ip_addr_t d = manual_ip.dns1;
dns_setserver(0, &d);
}
if (manual_ip.dns2.is_set()) {
ip_addr_t d = manual_ip.dns2;
dns_setserver(1, &d);
if (this->manual_ip_.has_value()) {
// Static IP was already configured before begin() in setup()
// Set DNS servers
LwIPLock lock;
if (this->manual_ip_->dns1.is_set()) {
ip_addr_t d;
d = this->manual_ip_->dns1;
dns_setserver(0, &d);
}
if (this->manual_ip_->dns2.is_set()) {
ip_addr_t d;
d = this->manual_ip_->dns2;
dns_setserver(1, &d);
}
}
#endif
}
+17
View File
@@ -21,6 +21,23 @@ void I2CBus::i2c_scan_() {
}
}
void I2CBus::dump_scan_results_() {
if (!this->scan_)
return;
ESP_LOGCONFIG(TAG, "Results from bus scan:");
if (this->scan_results_.empty()) {
ESP_LOGCONFIG(TAG, "Found no devices");
return;
}
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
void log_wrong_chip_id(uint8_t id, uint8_t expected) {
ESP_LOGE(TAG, "Wrong chip ID 0x%02X, expected 0x%02X", id, expected);
}
+2
View File
@@ -67,6 +67,8 @@ class I2CBus {
/// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair
/// that contains the address and the corresponding bool presence flag.
void i2c_scan_();
/// @brief Logs the scan results at config level, if the bus was scanned
void dump_scan_results_();
std::vector<std::pair<uint8_t, bool>> scan_results_; ///< array containing scan results
bool scan_{false}; ///< Should we scan ? Can be set in the yaml
};
+1 -14
View File
@@ -85,20 +85,7 @@ void ArduinoI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus");
break;
}
if (this->scan_) {
ESP_LOGI(TAG, "Results from bus scan:");
if (this->scan_results_.empty()) {
ESP_LOGI(TAG, "Found no devices");
} else {
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGI(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
this->dump_scan_results_();
}
ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
+1 -14
View File
@@ -127,20 +127,7 @@ void IDFI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus");
break;
}
if (this->scan_) {
ESP_LOGCONFIG(TAG, "Results from bus scan:");
if (this->scan_results_.empty()) {
ESP_LOGCONFIG(TAG, "Found no devices");
} else {
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
this->dump_scan_results_();
}
ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
+1 -8
View File
@@ -62,14 +62,7 @@ void HostI2CBus::dump_config() {
ESP_LOGE(TAG, " Setup Error: %s", this->first_error_.c_str());
}
if (this->scan_) {
ESP_LOGI(TAG, " Scan Results:");
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGI(TAG, " 0x%02X: Found", s.first);
}
}
}
this->dump_scan_results_();
}
ErrorCode HostI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
+1 -14
View File
@@ -51,20 +51,7 @@ void SoftwareI2CBus::dump_config() {
if (this->recovered_) {
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
}
if (this->scan_) {
ESP_LOGI(TAG, "Results from bus scan:");
if (this->scan_results_.empty()) {
ESP_LOGI(TAG, "Found no devices");
} else {
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGI(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
this->dump_scan_results_();
}
// Half of one clock period in microseconds, rounded up. The frequency is validated to be non-zero.
+1 -14
View File
@@ -60,20 +60,7 @@ void ZephyrI2CBus::dump_config() {
} else {
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
}
if (this->scan_) {
ESP_LOGI(TAG, "Results from I2C bus scan:");
if (scan_results_.empty()) {
ESP_LOGI(TAG, "Found no I2C devices!");
} else {
for (const auto &s : scan_results_) {
if (s.second) {
ESP_LOGI(TAG, "Found I2C device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
this->dump_scan_results_();
}
ErrorCode ZephyrI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
@@ -92,9 +92,5 @@ async def to_code(config: ConfigType) -> None:
await ota_to_code(var, config)
await cg.register_component(var, config)
cg.add_define("USE_WEBSERVER_OTA")
# web_server and prometheus create the server in an earlier setup(); without them
# the handler waits until captive_portal starts it
if not {"web_server", "prometheus"} & CORE.config.keys():
cg.add_define("USE_WEBSERVER_BASE_PENDING_HANDLER")
if CORE.is_esp32:
add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
@@ -1,6 +1,7 @@
#include "ota_web_server.h"
#ifdef USE_WEBSERVER_OTA
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -8,37 +9,73 @@
#include "esphome/components/captive_portal/captive_portal.h"
#endif
#if USE_ESP32
using PlatformString = std::string;
#elif USE_ARDUINO
using PlatformString = String;
#endif
namespace esphome::web_server {
ESPHOME_LOG_TAG(TAG, "web_server.ota");
bool WebServerOTAComponent::canHandle(AsyncWebServerRequest *request) const {
if (request->method() != HTTP_POST)
return false;
// Check if this is an OTA update request
class OTARequestHandler : public AsyncWebHandler {
public:
OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {}
void handleRequest(AsyncWebServerRequest *request) override;
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() != HTTP_POST)
return false;
// Check if this is an OTA update request
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
#else
bool is_ota_request = request->url() == ESPHOME_F("/update");
bool is_ota_request = request->url() == ESPHOME_F("/update");
#endif
#if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL)
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component
// Captive portal can still perform OTA updates - check if request is from active captive portal
// Note: global_captive_portal is the standard way components communicate in ESPHome
return is_ota_request && captive_portal::global_captive_portal != nullptr &&
captive_portal::global_captive_portal->is_active();
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component
// Captive portal can still perform OTA updates - check if request is from active captive portal
// Note: global_captive_portal is the standard way components communicate in ESPHome
return is_ota_request && captive_portal::global_captive_portal != nullptr &&
captive_portal::global_captive_portal->is_active();
#elif defined(USE_WEBSERVER_OTA_DISABLED)
// OTA disabled for web_server and no captive portal compiled in
return false;
// OTA disabled for web_server and no captive portal compiled in
return false;
#else
// OTA enabled for web_server
return is_ota_request;
// OTA enabled for web_server
return is_ota_request;
#endif
}
}
void WebServerOTAComponent::report_ota_progress_(AsyncWebServerRequest *request) {
// NOLINTNEXTLINE(readability-identifier-naming)
bool isRequestHandlerTrivial() const override { return false; }
protected:
void report_ota_progress_(AsyncWebServerRequest *request);
void schedule_ota_reboot_();
void ota_init_(const char *filename);
/// Abort and drop the backend, releasing the lock.
void abort_session_();
/// Reply "busy" without touching the running upload.
void refuse_(AsyncWebServerRequest *request);
uint32_t last_ota_progress_{0};
uint32_t ota_read_length_{0};
WebServerOTAComponent *parent_;
bool ota_success_{false};
private:
ota::OTABackendPtr ota_backend_{nullptr};
/// Request that owns ota_backend_.
const AsyncWebServerRequest *ota_request_{nullptr};
const AsyncWebServerRequest *refused_request_{nullptr};
};
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
const uint32_t now = millis();
if (now - this->last_ota_progress_ > 1000) {
float percentage = 0.0f;
@@ -54,28 +91,28 @@ void WebServerOTAComponent::report_ota_progress_(AsyncWebServerRequest *request)
}
#ifdef USE_OTA_STATE_LISTENER
// Report progress - use notify_state_deferred_ since we're in web server task
this->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0);
this->parent_->notify_state_deferred_(ota::OTA_IN_PROGRESS, percentage, 0);
#endif
this->last_ota_progress_ = now;
}
}
void WebServerOTAComponent::schedule_ota_reboot_() {
void OTARequestHandler::schedule_ota_reboot_() {
ESP_LOGI(TAG, "OTA update successful!");
this->set_timeout(100, []() {
this->parent_->set_timeout(100, []() {
ESP_LOGI(TAG, "Performing OTA reboot now");
App.safe_reboot();
});
}
void WebServerOTAComponent::ota_init_(const char *filename) {
void OTARequestHandler::ota_init_(const char *filename) {
ESP_LOGI(TAG, "OTA Update Start: %s", filename);
this->ota_read_length_ = 0;
this->ota_success_ = false;
}
void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index,
uint8_t *data, size_t len, bool final) {
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index,
uint8_t *data, size_t len, bool final) {
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
// First byte of a new upload: index==0 with actual data. (web_server_idf
@@ -93,7 +130,8 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
if (!this->ota_backend_) {
ESP_LOGE(TAG, "Failed to create OTA backend");
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f,
static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
#endif
return;
}
@@ -115,14 +153,14 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
#ifdef USE_OTA_STATE_LISTENER
// Notify OTA started - use notify_state_deferred_ since we're in web server task
this->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0);
this->parent_->notify_state_deferred_(ota::OTA_STARTED, 0.0f, 0);
#endif
if (error_code != ota::OTA_RESPONSE_OK) {
ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
this->ota_backend_.reset();
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
return;
}
@@ -150,7 +188,7 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
this->ota_backend_.reset();
this->ota_request_ = nullptr;
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
return;
}
@@ -171,7 +209,7 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
this->ota_success_ = true;
#ifdef USE_OTA_STATE_LISTENER
// Report completion before reboot - use notify_state_deferred_ since we're in web server task
this->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0);
this->parent_->notify_state_deferred_(ota::OTA_COMPLETED, 100.0f, 0);
#endif
this->schedule_ota_reboot_();
} else {
@@ -179,7 +217,7 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
// Reset the platform updater before the lock is released.
this->ota_backend_->abort();
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
}
this->ota_backend_.reset();
@@ -187,16 +225,16 @@ void WebServerOTAComponent::handleUpload(AsyncWebServerRequest *request, const P
}
}
void WebServerOTAComponent::abort_session_() {
void OTARequestHandler::abort_session_() {
this->ota_backend_->abort();
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_deferred_(ota::OTA_ABORT, 0.0f, 0);
this->parent_->notify_state_deferred_(ota::OTA_ABORT, 0.0f, 0);
#endif
this->ota_backend_.reset();
this->ota_request_ = nullptr;
}
void WebServerOTAComponent::refuse_(AsyncWebServerRequest *request) {
void OTARequestHandler::refuse_(AsyncWebServerRequest *request) {
this->refused_request_ = request;
request->onDisconnect([this, request]() {
if (this->refused_request_ == request)
@@ -204,7 +242,7 @@ void WebServerOTAComponent::refuse_(AsyncWebServerRequest *request) {
});
}
void WebServerOTAComponent::handleRequest(AsyncWebServerRequest *request) {
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response;
// Use the ota_success_ flag to determine the actual result
const bool refused = request == this->refused_request_;
@@ -239,7 +277,8 @@ void WebServerOTAComponent::setup() {
return;
}
base->add_handler(this);
// The handler lives for the life of the process; WebServerBase never destroys its server
base->add_handler(new OTARequestHandler(this)); // NOLINT
}
void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); }
@@ -3,44 +3,20 @@
#include "esphome/core/defines.h"
#ifdef USE_WEBSERVER_OTA
#include "esphome/components/ota/ota_backend_factory.h"
#include "esphome/components/ota/ota_backend.h"
#include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/component.h"
namespace esphome::web_server {
class WebServerOTAComponent final : public AsyncWebHandler, public ota::OTAComponent {
class WebServerOTAComponent final : public ota::OTAComponent {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void handleRequest(AsyncWebServerRequest *request) override;
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override;
// NOLINTNEXTLINE(readability-identifier-naming)
bool isRequestHandlerTrivial() const override { return false; }
protected:
void report_ota_progress_(AsyncWebServerRequest *request);
void schedule_ota_reboot_();
void ota_init_(const char *filename);
/// Abort and drop the backend, releasing the lock.
void abort_session_();
/// Reply "busy" without touching the running upload.
void refuse_(AsyncWebServerRequest *request);
uint32_t last_ota_progress_{0};
uint32_t ota_read_length_{0};
bool ota_success_{false};
private:
ota::OTABackendPtr ota_backend_{nullptr};
/// Request that owns ota_backend_.
const AsyncWebServerRequest *ota_request_{nullptr};
const AsyncWebServerRequest *refused_request_{nullptr};
friend class OTARequestHandler;
};
} // namespace esphome::web_server
@@ -72,7 +72,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}"))
if CORE.is_esp32:
# Count for StaticVector in web_server_idf - matches headers added in create_server_()
# Count for StaticVector in web_server_idf - matches headers added in init()
cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1)
return
@@ -15,29 +15,10 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
}
void WebServerBase::add_handler_without_auth(AsyncWebHandler *handler) {
if (this->server_ == nullptr) {
#ifdef USE_WEBSERVER_BASE_PENDING_HANDLER
if (this->pending_handler_ == nullptr) {
this->pending_handler_ = handler;
return;
}
#endif
// Nowhere for it to wait: create the server now, after the waiting handler to keep the order
this->create_server_();
this->handlers_.push_back(handler);
if (this->server_ != nullptr) {
this->server_->addHandler(handler);
}
this->server_->addHandler(handler);
}
void WebServerBase::create_server_() {
this->server_ = new AsyncWebServer(this->port_);
// All content is controlled and created by user - so allowing all origins is fine here.
// NOTE: Currently 1 header. If more are added, update in __init__.py:
// cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1)
DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*"));
#ifdef USE_WEBSERVER_BASE_PENDING_HANDLER
if (this->pending_handler_ != nullptr)
this->server_->addHandler(this->pending_handler_);
#endif
}
} // namespace esphome::web_server_base
@@ -1,6 +1,8 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_NETWORK) && !defined(USE_ZEPHYR) && !defined(USE_HOST)
#include <vector>
#include "esphome/core/progmem.h"
#if USE_ESP32
@@ -113,14 +115,26 @@ class WebServerBase final {
// The AsyncWebServer is created once and intentionally never deleted: on Arduino
// platforms ESPAsyncWebServer owns its registered handlers, so destroying it would
// also destroy live components (e.g. the captive portal) out from under us.
// init()/deinit() refcount users and start/stop the listener; handlers stay
// registered across listener restarts.
// init()/deinit() refcount users and start/stop the listener; handlers are
// registered once at creation and survive listener restarts.
void init() {
if (++this->initialized_ != 1)
this->initialized_++;
if (this->server_ != nullptr) {
if (this->initialized_ == 1) {
// Restart the listener after a previous deinit()
this->server_->begin();
}
return;
if (this->server_ == nullptr)
this->create_server_();
}
this->server_ = new AsyncWebServer(this->port_);
// All content is controlled and created by user - so allowing all origins is fine here.
// NOTE: Currently 1 header. If more are added, update in __init__.py:
// cg.add_define("WEB_SERVER_DEFAULT_HEADERS_COUNT", 1)
DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*"));
this->server_->begin();
for (auto *handler : this->handlers_)
this->server_->addHandler(handler);
}
void deinit() {
if (this->initialized_ == 0)
@@ -155,15 +169,10 @@ class WebServerBase final {
uint16_t get_port() const { return port_; }
protected:
void create_server_();
uint8_t initialized_{0};
uint16_t port_{80}; // Keep in sync with DEFAULT_PORT in web_server/__init__.py
AsyncWebServer *server_{nullptr};
#ifdef USE_WEBSERVER_BASE_PENDING_HANDLER
// Web OTA's handler when it is added before any component created the server
AsyncWebHandler *pending_handler_{nullptr};
#endif
std::vector<AsyncWebHandler *> handlers_;
#ifdef USE_WEBSERVER_AUTH
internal::Credentials credentials_;
#endif
+29 -33
View File
@@ -1,4 +1,3 @@
from ipaddress import IPv4Address
import logging
import math
from typing import Any
@@ -20,6 +19,7 @@ from esphome.components.network import (
add_use_address,
get_network_priority,
has_high_performance_wifi,
ip_address_literal,
require_high_performance_wifi,
require_ipv4,
)
@@ -190,6 +190,7 @@ DEFAULT_DPP_TIMEOUT = "1min"
wifi_ns = cg.esphome_ns.namespace("wifi")
EAPAuth = wifi_ns.struct("EAPAuth")
ManualIP = wifi_ns.struct("ManualIP")
WiFiComponent = wifi_ns.class_("WiFiComponent", cg.Component)
WiFiAP = wifi_ns.struct("WiFiAP")
@@ -803,20 +804,24 @@ def eap_auth(config):
)
def manual_ip_entry(config: ConfigType) -> bytes:
"""Pack manual IP settings as one MANUAL_IP_TABLE row (MANUAL_IP_ENTRY_SIZE bytes)."""
no_dns = IPv4Address(0)
addresses = (
config[CONF_STATIC_IP],
config[CONF_GATEWAY],
config[CONF_SUBNET],
config.get(CONF_DNS1, no_dns),
config.get(CONF_DNS2, no_dns),
def safe_ip(ip):
return ip_address_literal(ip)
def manual_ip(config):
if config is None:
return None
return cg.StructInitializer(
ManualIP,
("static_ip", safe_ip(config[CONF_STATIC_IP])),
("gateway", safe_ip(config[CONF_GATEWAY])),
("subnet", safe_ip(config[CONF_SUBNET])),
("dns1", safe_ip(config.get(CONF_DNS1))),
("dns2", safe_ip(config.get(CONF_DNS2))),
)
return b"".join(address.packed for address in addresses)
def wifi_network(config, ap, manual_ip_index: int | None):
def wifi_network(config, ap, static_ip):
cg.add_optional(ap.set_ssid, config.get(CONF_SSID))
cg.add_optional(ap.set_password, config.get(CONF_PASSWORD))
if CONF_EAP in config:
@@ -826,7 +831,8 @@ def wifi_network(config, ap, manual_ip_index: int | None):
cg.add(ap.set_bssid([HexInt(i) for i in config[CONF_BSSID].parts]))
cg.add_optional(ap.set_hidden, config.get(CONF_HIDDEN))
cg.add_optional(ap.set_channel, config.get(CONF_CHANNEL))
cg.add_optional(ap.set_manual_ip, manual_ip_index)
if static_ip is not None:
cg.add(ap.set_manual_ip(manual_ip(static_ip)))
# priority_ is 0 in C++; skip the setter when the config matches it.
if config.get(CONF_PRIORITY, 0) != 0:
cg.add(ap.set_priority(config[CONF_PRIORITY]))
@@ -845,17 +851,8 @@ async def to_code(config):
# Track if any network uses Enterprise authentication
has_eap = False
# Rows of MANUAL_IP_TABLE; networks with the same settings share one. At most
# MAX_WIFI_NETWORKS + 1 (the AP) rows, so an index never reaches MANUAL_IP_NONE (0xFF).
manual_ip_entries: list[bytes] = []
def manual_ip_index(ip_config: ConfigType | None) -> int | None:
if ip_config is None:
return None
entry = manual_ip_entry(ip_config)
if entry not in manual_ip_entries:
manual_ip_entries.append(entry)
return manual_ip_entries.index(entry)
# Track if any network uses manual IP
has_manual_ip = False
# Initialize FixedVector with the count of networks
networks = config.get(CONF_NETWORKS, [])
@@ -867,20 +864,24 @@ async def to_code(config):
def add_sta(ap: cg.MockObj, network: dict) -> None:
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
cg.add(var.add_sta(wifi_network(network, ap, manual_ip_index(ip_config))))
cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
for network in networks:
if CONF_EAP in network:
has_eap = True
if network.get(CONF_MANUAL_IP) or config.get(CONF_MANUAL_IP):
has_manual_ip = True
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
if CONF_AP in config:
conf = config[CONF_AP]
ap_ip_index = manual_ip_index(conf.get(CONF_MANUAL_IP))
ip_config = conf.get(CONF_MANUAL_IP)
if ip_config:
has_manual_ip = True
cg.with_local_variable(
conf[CONF_ID],
WiFiAP(),
lambda ap: cg.add(var.set_ap(wifi_network(conf, ap, ap_ip_index))),
lambda ap: cg.add(var.set_ap(wifi_network(conf, ap, ip_config))),
)
# Skip the setter when the config matches the C++ initializer.
if (ap_timeout := conf[CONF_AP_TIMEOUT]) != DEFAULT_AP_TIMEOUT_PERIOD:
@@ -938,13 +939,8 @@ async def to_code(config):
require_mbedtls_tls_extras()
# Only define USE_WIFI_MANUAL_IP if any AP uses manual IP
if manual_ip_entries:
if has_manual_ip:
cg.add_define("USE_WIFI_MANUAL_IP")
cg.extern_progmem_array(
"esphome::wifi::MANUAL_IP_TABLE",
cg.uint8,
list(b"".join(manual_ip_entries)),
)
# The C++ initializers are DEFAULT_REBOOT_TIMEOUT, power save NONE and minimum
# auth WPA2; skip the setters when the config matches them.
+7 -16
View File
@@ -1360,15 +1360,17 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
ESP_LOGV(TAG, " Channel not set");
}
#ifdef USE_WIFI_MANUAL_IP
if (const auto m = ap.get_manual_ip(); m.has_value()) {
auto manual_ip = ap.get_manual_ip();
if (manual_ip.has_value()) {
ManualIP m = *manual_ip;
char static_ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m->static_ip.str_to(static_ip_buf),
m->gateway.str_to(gateway_buf), m->subnet.str_to(subnet_buf), m->dns1.str_to(dns1_buf),
m->dns2.str_to(dns2_buf));
ESP_LOGV(TAG, " Manual IP: Static IP=%s Gateway=%s Subnet=%s DNS1=%s DNS2=%s", m.static_ip.str_to(static_ip_buf),
m.gateway.str_to(gateway_buf), m.subnet.str_to(subnet_buf), m.dns1.str_to(dns1_buf),
m.dns2.str_to(dns2_buf));
} else
#endif
{
@@ -1906,7 +1908,7 @@ void WiFiComponent::check_connecting_finished(uint32_t now) {
#if defined(USE_ESP8266) && defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
// On ESP8266, GOT_IP event may not fire for static IP configurations,
// so notify IP state listeners here as a fallback.
if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_manual_ip()) {
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
this->notify_ip_state_listeners_();
}
#endif
@@ -2879,17 +2881,6 @@ void WiFiComponent::process_roaming_scan_() {
this->start_connecting(roam_params);
}
#ifdef USE_WIFI_MANUAL_IP
optional<ManualIP> WiFiAP::get_manual_ip() const {
if (!this->has_manual_ip())
return {};
uint8_t b[MANUAL_IP_ENTRY_SIZE];
progmem_memcpy(b, MANUAL_IP_TABLE + this->manual_ip_index_ * MANUAL_IP_ENTRY_SIZE, sizeof(b));
auto ip = [&b](size_t i) { return network::IPAddress(b[i], b[i + 1], b[i + 2], b[i + 3]); };
return ManualIP{ip(0), ip(4), ip(8), ip(12), ip(16)};
}
#endif
WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome::wifi
+7 -17
View File
@@ -188,14 +188,6 @@ struct ManualIP {
network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default.
};
#ifdef USE_WIFI_MANUAL_IP
/// One MANUAL_IP_TABLE row: static IP, gateway, subnet, dns1 and dns2 as 4 octets each.
static constexpr size_t MANUAL_IP_ENTRY_SIZE = 20;
static constexpr uint8_t MANUAL_IP_NONE = 0xFF;
/// Every configured manual IP, generated by codegen (components/wifi/__init__.py) and kept in flash.
extern const uint8_t MANUAL_IP_TABLE[] PROGMEM;
#endif
#ifdef USE_WIFI_WPA2_EAP
struct EAPAuth {
std::string identity; // required for all auth types
@@ -308,8 +300,7 @@ class WiFiAP {
void clear_channel() { this->channel_ = 0; }
void set_priority(int8_t priority) { priority_ = priority; }
#ifdef USE_WIFI_MANUAL_IP
/// index is a row of MANUAL_IP_TABLE
void set_manual_ip(uint8_t index) { this->manual_ip_index_ = index; }
void set_manual_ip(optional<ManualIP> manual_ip) { this->manual_ip_ = manual_ip; }
#endif
void set_hidden(bool hidden) { this->hidden_ = hidden; }
StringRef get_ssid() const { return this->ssid_.ref(); }
@@ -323,8 +314,7 @@ class WiFiAP {
bool has_channel() const { return this->channel_ != 0; }
int8_t get_priority() const { return priority_; }
#ifdef USE_WIFI_MANUAL_IP
bool has_manual_ip() const { return this->manual_ip_index_ != MANUAL_IP_NONE; }
optional<ManualIP> get_manual_ip() const;
const optional<ManualIP> &get_manual_ip() const { return this->manual_ip_; }
#endif
bool get_hidden() const { return this->hidden_; }
@@ -334,14 +324,14 @@ class WiFiAP {
#ifdef USE_WIFI_WPA2_EAP
optional<EAPAuth> eap_;
#endif // USE_WIFI_WPA2_EAP
#ifdef USE_WIFI_MANUAL_IP
optional<ManualIP> manual_ip_;
#endif
// Group small types together to minimize padding
bssid_t bssid_{}; // 6 bytes, all zeros = any/not set
uint8_t channel_{0}; // 1 byte, 0 = auto/not set
int8_t priority_{0}; // 1 byte
bool hidden_{false}; // 1 byte; WiFiAP is byte aligned unless EAP adds a 4 byte aligned member
#ifdef USE_WIFI_MANUAL_IP
uint8_t manual_ip_index_{MANUAL_IP_NONE};
#endif
bool hidden_{false}; // 1 byte; WiFiAP is byte aligned unless manual IP or EAP adds a 4 byte aligned member
};
class WiFiScanResult {
@@ -1060,7 +1050,7 @@ class WiFiComponent final : public Component {
uint32_t roaming_scan_end_{0}; // Timestamp when last roaming scan completed
#ifdef USE_WIFI_AP
uint32_t ap_timeout_{90000}; // Keep in sync with DEFAULT_AP_TIMEOUT in __init__.py
// WiFiAP is byte aligned unless EAP is enabled; placed before the
// WiFiAP is byte aligned unless manual IP or EAP is enabled; placed before the
// 1-byte members so they pack into its trailing bytes instead of padding after it
WiFiAP ap_;
#endif // USE_WIFI_AP
@@ -795,7 +795,7 @@ void WiFiComponent::save_lease_settings_() {
return;
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_manual_ip())
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value())
return;
#endif
@@ -1093,7 +1093,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
bool static_ip = false;
#ifdef USE_WIFI_MANUAL_IP
const WiFiAP *config = this->get_selected_sta_();
static_ip = config && config->has_manual_ip();
static_ip = config && config->get_manual_ip().has_value();
#endif
#ifdef USE_WIFI_LEASE_CACHE
static_ip = static_ip || this->applied_cached_lease_;
@@ -510,7 +510,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
#endif
// For static IP configurations, GOT_IP event may not fire, so set connected state here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_manual_ip()) {
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
this->sta_state_ = static_cast<uint8_t>(LTWiFiSTAState::CONNECTED);
#ifdef USE_WIFI_IP_STATE_LISTENERS
this->notify_ip_state_listeners_();
@@ -342,7 +342,7 @@ bool WiFiComponent::wifi_loop_() {
#endif
// For static IP configurations, notify IP listeners immediately as the IP is already configured
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_manual_ip()) {
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
s_sta_had_ip = true;
this->notify_ip_state_listeners_();
}
-2
View File
@@ -561,7 +561,6 @@
#define USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
#define USE_WEBSERVER_VERSION 3
#define WEB_SERVER_DEFAULT_HEADERS_COUNT 1
#define USE_WEBSERVER_BASE_PENDING_HANDLER
#define USE_CAPTIVE_PORTAL_GZIP
#endif
#define USE_WIFI_11KV_SUPPORT
@@ -741,7 +740,6 @@
#define USE_WEBSERVER_VERSION 2
#define USE_WEBSERVER_LOCAL
#define USE_WEBSERVER_OTA
#define USE_WEBSERVER_BASE_PENDING_HANDLER
#define USE_WEBSERVER_OTA_DISABLED
#endif
@@ -1,18 +0,0 @@
esphome:
name: test
esp32:
board: esp32dev
ethernet:
type: LAN8720
mdc_pin: GPIO23
mdio_pin: GPIO18
clk:
mode: CLK_EXT_IN
pin: GPIO0
manual_ip:
static_ip: 192.168.1.50
gateway: 192.168.1.1
subnet: 255.255.255.0
dns1: 1.1.1.1
@@ -1,29 +0,0 @@
"""The manual IP table codegen emits must match the ETHERNET_MANUAL_IP layout in ethernet_component.h."""
from collections.abc import Callable
from pathlib import Path
def test_manual_ip_table_layout(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Twenty octets: static IP, gateway, subnet, dns1, dns2 (unset DNS is zero)."""
main_cpp = generate_main(component_config_path("manual_ip.yaml"))
expected = (
"192, 168, 1, 50, 192, 168, 1, 1, 255, 255, 255, 0, 1, 1, 1, 1, 0, 0, 0, 0"
)
assert (
f"esphome::ethernet::ETHERNET_MANUAL_IP[] PROGMEM = {{{expected}}};" in main_cpp
)
assert "set_manual_ip(" not in main_cpp
def test_no_manual_ip_emits_no_table(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("rmii_defaults.yaml"))
assert "ETHERNET_MANUAL_IP" not in main_cpp
@@ -1,14 +0,0 @@
---
esphome:
name: test
esp8266:
board: d1_mini
wifi:
ssid: test
password: testtest
ap:
password: testtest
captive_portal:
@@ -1,16 +0,0 @@
---
esphome:
name: test
esp8266:
board: d1_mini
wifi:
ssid: test
password: testtest
ap:
password: testtest
captive_portal:
prometheus:
@@ -1,16 +0,0 @@
---
esphome:
name: test
esp8266:
board: d1_mini
wifi:
ssid: test
password: testtest
ap:
password: testtest
captive_portal:
web_server:
@@ -1,34 +0,0 @@
"""Tests for when web OTA's handler waits in WebServerBase for the server to be created."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.core import CORE
_PENDING = "USE_WEBSERVER_BASE_PENDING_HANDLER"
@pytest.mark.parametrize(
("config_file", "pending"),
[
# captive_portal loads web OTA, and with it the web_server package, but only
# creates the server when the AP starts
("pending_handler_captive_portal.yaml", True),
("pending_handler_web_server.yaml", False),
("pending_handler_prometheus.yaml", False),
],
)
def test_pending_handler_define(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
config_file: str,
pending: bool,
) -> None:
"""Only configs where nothing creates the server before web OTA's setup() keep the pointer."""
generate_main(component_config_path(config_file))
defines = {define.name for define in CORE.defines}
assert "USE_WEBSERVER_OTA" in defines
assert (_PENDING in defines) is pending
@@ -1,32 +0,0 @@
esphome:
name: test
esp8266:
board: d1_mini
wifi:
# Inherited by every network without its own manual_ip
manual_ip:
static_ip: 192.168.2.20
gateway: 192.168.2.1
subnet: 255.255.255.0
dns1: 192.168.2.1
networks:
- ssid: own
password: password1
manual_ip:
static_ip: 192.168.1.10
gateway: 192.168.1.1
subnet: 255.255.255.0
dns1: 1.1.1.1
dns2: 8.8.8.8
- ssid: inherits1
password: password2
- ssid: inherits2
password: password3
ap:
password: fallback_password
manual_ip:
static_ip: 192.168.4.1
gateway: 192.168.4.1
subnet: 255.255.255.0
@@ -1,25 +0,0 @@
"""Tests for the manual IP table wifi codegen emits (MANUAL_IP_TABLE in wifi_component.h)."""
from collections.abc import Callable
from pathlib import Path
import re
def test_manual_ip_rows_are_shared(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Networks with the same settings share a row; the AP row packs its missing DNS as zeros."""
main_cpp = generate_main(component_config_path("manual_ip.yaml"))
own = "192, 168, 1, 10, 192, 168, 1, 1, 255, 255, 255, 0, 1, 1, 1, 1, 8, 8, 8, 8"
inherited = (
"192, 168, 2, 20, 192, 168, 2, 1, 255, 255, 255, 0, 192, 168, 2, 1, 0, 0, 0, 0"
)
ap = "192, 168, 4, 1, 192, 168, 4, 1, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0"
assert (
f"esphome::wifi::MANUAL_IP_TABLE[] PROGMEM = {{{own}, {inherited}, {ap}}};"
in main_cpp
)
# Three networks in config order, then the AP
assert re.findall(r"set_manual_ip\((\d+)\)", main_cpp) == ["0", "1", "1", "2"]
-5
View File
@@ -42,11 +42,6 @@ wifi:
- ssid: MySSID2
password: password2
priority: 5
manual_ip:
static_ip: 192.168.2.100
gateway: 192.168.2.1
subnet: 255.255.255.0
dns1: 192.168.2.1
- ssid: MySSID3
password: password3
priority: 0
@@ -5,10 +5,6 @@ wifi:
ap:
ssid: Fallback AP
password: fallback_password
manual_ip:
static_ip: 192.168.4.1
gateway: 192.168.4.1
subnet: 255.255.255.0
packages:
- !include common.yaml