mirror of
https://github.com/esphome/esphome.git
synced 2026-10-27 16:32:11 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59e8b0b9c2 | ||
|
|
5b12ff419e | ||
|
|
3765f9072e |
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_();
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
Reference in new issue
Block a user