mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
Merge branch 'runtime_use_address' into integration
This commit is contained in:
@@ -240,12 +240,13 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() {
|
||||
}
|
||||
|
||||
void APIServer::dump_config() {
|
||||
char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Server:\n"
|
||||
" Address: %s:%u\n"
|
||||
" Listen backlog: %u\n"
|
||||
" Max connections: %u",
|
||||
network::get_use_address(), this->port_, this->listen_backlog_, MAX_API_CONNECTIONS);
|
||||
network::get_use_address_to(addr_buf), this->port_, this->listen_backlog_, MAX_API_CONNECTIONS);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk()));
|
||||
if (!this->noise_ctx_.has_psk()) {
|
||||
|
||||
@@ -94,11 +94,12 @@ void ESPHomeOTAComponent::setup() {
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::dump_config() {
|
||||
char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Over-The-Air updates:\n"
|
||||
" Address: %s:%u\n"
|
||||
" Version: %d",
|
||||
network::get_use_address(), this->port_, USE_OTA_VERSION);
|
||||
network::get_use_address_to(addr_buf), this->port_, USE_OTA_VERSION);
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
if (!this->password_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Password configured");
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
from esphome import automation, pins
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.network import ip_address_literal
|
||||
from esphome.components.network import add_use_address, ip_address_literal
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -543,7 +543,7 @@ async def to_code(config):
|
||||
await _to_code_rp2040(var, config)
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
add_use_address(var, config[CONF_USE_ADDRESS])
|
||||
# enable_on_boot defaults to true in C++ - only set if false
|
||||
if not config[CONF_ENABLE_ON_BOOT]:
|
||||
cg.add(var.set_enable_on_boot(False))
|
||||
|
||||
@@ -145,6 +145,8 @@ class EthernetComponent final : public Component {
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
network::IPAddress get_dns_address(uint8_t num);
|
||||
/// Returns nullptr when no explicit use_address is configured and the address is
|
||||
/// derived at runtime from the device name (see network::get_use_address_to()).
|
||||
const char *get_use_address() const { return this->use_address_; }
|
||||
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
@@ -346,7 +348,7 @@ class EthernetComponent final : public Component {
|
||||
private:
|
||||
// Stores a pointer to a string literal (static storage duration).
|
||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||
const char *use_address_{""};
|
||||
const char *use_address_{nullptr};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -59,6 +59,19 @@ def ip_address_literal(ip: str | int | None) -> cg.MockObj:
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
def add_use_address(var: cg.MockObj, use_address: str) -> None:
|
||||
"""Generate a set_use_address() call only when the address must be baked in.
|
||||
|
||||
The default "<name>.local" is not stored in the firmware; it is rebuilt at
|
||||
runtime from the device name (see network::get_use_address_to()), which also
|
||||
picks up the MAC suffix when name_add_mac_suffix is enabled. A compile-time
|
||||
string could never include that suffix, so baking it in would log the wrong
|
||||
address.
|
||||
"""
|
||||
if use_address != f"{CORE.name}.local":
|
||||
cg.add(var.set_use_address(use_address))
|
||||
|
||||
|
||||
def require_high_performance_networking() -> None:
|
||||
"""Request high performance networking for network and WiFi.
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#ifdef USE_NETWORK
|
||||
|
||||
namespace esphome::network {
|
||||
@@ -20,6 +22,27 @@ bool is_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *get_use_address_to(std::span<char, USE_ADDRESS_BUFFER_SIZE> buf) {
|
||||
// Global component pointers are guaranteed to be set by component constructors when USE_* is defined
|
||||
const char *addr = nullptr;
|
||||
#if defined(USE_ETHERNET)
|
||||
addr = ethernet::global_eth_component->get_use_address();
|
||||
#elif defined(USE_MODEM)
|
||||
addr = modem::global_modem_component->get_use_address();
|
||||
#elif defined(USE_WIFI)
|
||||
addr = wifi::global_wifi_component->get_use_address();
|
||||
#elif defined(USE_OPENTHREAD)
|
||||
addr = openthread::global_openthread_component->get_use_address();
|
||||
#endif
|
||||
if (addr != nullptr && addr[0] != '\0')
|
||||
return addr;
|
||||
// No explicit use_address configured: the address is the runtime device name
|
||||
// (which includes the MAC suffix when name_add_mac_suffix is enabled) plus ".local"
|
||||
const auto &name = App.get_name();
|
||||
make_name_with_suffix_to(buf.data(), buf.size(), name.c_str(), name.size(), '.', "local", 5);
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
network::IPAddresses get_ip_addresses() {
|
||||
#ifdef USE_ETHERNET
|
||||
if (ethernet::global_eth_component != nullptr)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_NETWORK
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "ip_address.h"
|
||||
@@ -53,30 +54,12 @@ ESPHOME_ALWAYS_INLINE inline bool is_connected() {
|
||||
|
||||
/// Return whether the network is disabled (only wifi for now)
|
||||
bool is_disabled();
|
||||
/// Get the active network hostname
|
||||
ESPHOME_ALWAYS_INLINE inline const char *get_use_address() {
|
||||
// Global component pointers are guaranteed to be set by component constructors when USE_* is defined
|
||||
#ifdef USE_ETHERNET
|
||||
return ethernet::global_eth_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#ifdef USE_MODEM
|
||||
return modem::global_modem_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#ifdef USE_WIFI
|
||||
return wifi::global_wifi_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENTHREAD
|
||||
return openthread::global_openthread_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD)
|
||||
// Fallback when no network component is defined (e.g., host platform)
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
/// Buffer size for get_use_address_to(): 63-char DNS label + ".local" + null terminator
|
||||
static constexpr size_t USE_ADDRESS_BUFFER_SIZE = 70;
|
||||
/// Get the active network address for logging. Returns the explicitly configured
|
||||
/// use_address when one was set, otherwise formats "<name>.local" from the runtime
|
||||
/// device name into buf (so it includes the MAC suffix from name_add_mac_suffix).
|
||||
const char *get_use_address_to(std::span<char, USE_ADDRESS_BUFFER_SIZE> buf);
|
||||
IPAddresses get_ip_addresses();
|
||||
|
||||
} // namespace esphome::network
|
||||
|
||||
@@ -14,6 +14,7 @@ from esphome.components.esp32 import (
|
||||
require_vfs_select,
|
||||
)
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
from esphome.components.network import add_use_address
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
@@ -288,7 +289,7 @@ async def to_code(config):
|
||||
enable_mdns_storage()
|
||||
|
||||
ot = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
add_use_address(ot, config[CONF_USE_ADDRESS])
|
||||
await cg.register_component(ot, config)
|
||||
if (poll_period := config.get(CONF_POLL_PERIOD)) is not None:
|
||||
cg.add(ot.set_poll_period(poll_period))
|
||||
|
||||
@@ -39,6 +39,8 @@ class OpenThreadComponent final : public Component {
|
||||
void on_factory_reset(std::function<void()> callback);
|
||||
void defer_factory_reset_external_callback();
|
||||
|
||||
/// Returns nullptr when no explicit use_address is configured and the address is
|
||||
/// derived at runtime from the device name (see network::get_use_address_to()).
|
||||
const char *get_use_address() const { return this->use_address_; }
|
||||
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
#if CONFIG_OPENTHREAD_MTD
|
||||
@@ -76,7 +78,7 @@ class OpenThreadComponent final : public Component {
|
||||
private:
|
||||
// Stores a pointer to a string literal (static storage duration).
|
||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||
const char *use_address_{""};
|
||||
const char *use_address_{nullptr};
|
||||
};
|
||||
|
||||
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -421,10 +421,11 @@ void WebServer::on_log(uint8_t level, const char *tag, const char *message, size
|
||||
#endif
|
||||
|
||||
void WebServer::dump_config() {
|
||||
char addr_buf[network::USE_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Web Server:\n"
|
||||
" Address: %s:%u",
|
||||
network::get_use_address(), this->base_->get_port());
|
||||
network::get_use_address_to(addr_buf), this->base_->get_port());
|
||||
}
|
||||
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from esphome.components.esp32 import (
|
||||
request_wifi,
|
||||
)
|
||||
from esphome.components.network import (
|
||||
add_use_address,
|
||||
has_high_performance_networking,
|
||||
ip_address_literal,
|
||||
)
|
||||
@@ -585,7 +586,7 @@ def wifi_network(config, ap, static_ip):
|
||||
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
add_use_address(var, config[CONF_USE_ADDRESS])
|
||||
|
||||
# Track if any network uses Enterprise authentication
|
||||
has_eap = False
|
||||
|
||||
@@ -501,6 +501,8 @@ class WiFiComponent final : public Component {
|
||||
|
||||
network::IPAddress get_dns_address(int num);
|
||||
network::IPAddresses get_ip_addresses();
|
||||
/// Returns nullptr when no explicit use_address is configured and the address is
|
||||
/// derived at runtime from the device name (see network::get_use_address_to()).
|
||||
const char *get_use_address() const { return this->use_address_; }
|
||||
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
|
||||
@@ -996,7 +998,7 @@ class WiFiComponent final : public Component {
|
||||
private:
|
||||
// Stores a pointer to a string literal (static storage duration).
|
||||
// ONLY set from Python-generated code with string literals - never dynamic strings.
|
||||
const char *use_address_{""};
|
||||
const char *use_address_{nullptr};
|
||||
};
|
||||
|
||||
extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
esphome:
|
||||
name: use-address-runtime
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
@@ -0,0 +1,9 @@
|
||||
esphome:
|
||||
name: use-address-mac
|
||||
name_add_mac_suffix: true
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Integration tests for the runtime-built use_address.
|
||||
|
||||
The default "<name>.local" address is no longer stored as a compile-time string;
|
||||
it is built at runtime from the device name. This also fixes the logged address
|
||||
when name_add_mac_suffix is enabled: the baked string used to miss the MAC
|
||||
suffix, so it never matched the actual mDNS hostname.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
# Host platform default MAC: 98:35:69:ab:f6:79 -> suffix "abf679"
|
||||
MAC_SUFFIX = "abf679"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_use_address_runtime(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""The API dump_config logs "<name>.local" built from the device name."""
|
||||
address_seen = asyncio.Event()
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
if "Address: use-address-runtime.local:" in line:
|
||||
address_seen.set()
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "use-address-runtime"
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(address_seen.wait(), timeout=10.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("Did not log 'Address: use-address-runtime.local:'")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_use_address_runtime_mac_suffix(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""With name_add_mac_suffix the logged address includes the MAC suffix."""
|
||||
address_seen = asyncio.Event()
|
||||
expected = f"Address: use-address-mac-{MAC_SUFFIX}.local:"
|
||||
|
||||
def check_output(line: str) -> None:
|
||||
if expected in line:
|
||||
address_seen.set()
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=check_output),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == f"use-address-mac-{MAC_SUFFIX}"
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(address_seen.wait(), timeout=10.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(f"Did not log '{expected}'")
|
||||
Reference in New Issue
Block a user