mirror of
https://github.com/esphome/esphome.git
synced 2026-05-28 21:59:59 +08:00
[core] Replace Application name/friendly_name std::string with StringRef (#14532)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -269,7 +269,7 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
if (state_ == State::SERVER_HELLO) {
|
if (state_ == State::SERVER_HELLO) {
|
||||||
// send server hello
|
// send server hello
|
||||||
const std::string &name = App.get_name();
|
const auto &name = App.get_name();
|
||||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||||
get_mac_address_into_buffer(mac);
|
get_mac_address_into_buffer(mac);
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ bool ESP32BLE::ble_setup_() {
|
|||||||
device_name = this->name_;
|
device_name = this->name_;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const std::string &app_name = App.get_name();
|
const auto &app_name = App.get_name();
|
||||||
size_t name_len = app_name.length();
|
size_t name_len = app_name.length();
|
||||||
if (name_len > 20) {
|
if (name_len > 20) {
|
||||||
if (App.is_name_add_mac_suffix_enabled()) {
|
if (App.is_name_add_mac_suffix_enabled()) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
|||||||
service.proto = MDNS_STR(SERVICE_TCP);
|
service.proto = MDNS_STR(SERVICE_TCP);
|
||||||
service.port = api::global_api_server->get_port();
|
service.port = api::global_api_server->get_port();
|
||||||
|
|
||||||
const std::string &friendly_name = App.get_friendly_name();
|
const auto &friendly_name = App.get_friendly_name();
|
||||||
bool friendly_name_empty = friendly_name.empty();
|
bool friendly_name_empty = friendly_name.empty();
|
||||||
|
|
||||||
// Calculate exact capacity for txt_records
|
// Calculate exact capacity for txt_records
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
root[MQTT_UNIQUE_ID] = unique_id_buf;
|
root[MQTT_UNIQUE_ID] = unique_id_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &node_name = App.get_name();
|
const auto &node_name = App.get_name();
|
||||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
|
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
|
||||||
// node_name (max 31) + "_" (1) + object_id (max 128) + null
|
// node_name (max 31) + "_" (1) + object_id (max 128) + null
|
||||||
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
|
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
|
||||||
@@ -275,8 +275,8 @@ bool MQTTComponent::send_discovery_() {
|
|||||||
root[MQTT_OBJECT_ID] = object_id_full;
|
root[MQTT_OBJECT_ID] = object_id_full;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &friendly_name_ref = App.get_friendly_name();
|
const auto &friendly_name_ref = App.get_friendly_name();
|
||||||
const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
|
const auto &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
|
||||||
const char *node_area = App.get_area();
|
const char *node_area = App.get_area();
|
||||||
|
|
||||||
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ void OpenThreadSrpComponent::setup() {
|
|||||||
// set the host name
|
// set the host name
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
char *existing_host_name = otSrpClientBuffersGetHostNameString(instance, &size);
|
char *existing_host_name = otSrpClientBuffersGetHostNameString(instance, &size);
|
||||||
const std::string &host_name = App.get_name();
|
const auto &host_name = App.get_name();
|
||||||
uint16_t host_name_len = host_name.size();
|
uint16_t host_name_len = host_name.size();
|
||||||
if (host_name_len > size) {
|
if (host_name_len > size) {
|
||||||
ESP_LOGW(TAG, "Hostname is too long, choose a shorter project name");
|
ESP_LOGW(TAG, "Hostname is too long, choose a shorter project name");
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; }
|
|||||||
|
|
||||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||||
AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("text/html"));
|
AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("text/html"));
|
||||||
const std::string &title = App.get_name();
|
const auto &title = App.get_name();
|
||||||
stream->print(ESPHOME_F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
|
stream->print(ESPHOME_F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
|
||||||
"name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
|
"name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
|
||||||
stream->print(title.c_str());
|
stream->print(title.c_str());
|
||||||
|
|||||||
@@ -913,7 +913,7 @@ void WiFiComponent::setup_ap_config_() {
|
|||||||
static constexpr size_t AP_SSID_PREFIX_LEN = 25;
|
static constexpr size_t AP_SSID_PREFIX_LEN = 25;
|
||||||
static constexpr size_t AP_SSID_SUFFIX_LEN = 7;
|
static constexpr size_t AP_SSID_SUFFIX_LEN = 7;
|
||||||
|
|
||||||
const std::string &app_name = App.get_name();
|
const auto &app_name = App.get_name();
|
||||||
const char *name_ptr = app_name.c_str();
|
const char *name_ptr = app_name.c_str();
|
||||||
size_t name_len = app_name.length();
|
size_t name_len = app_name.length();
|
||||||
|
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
|||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
bool WiFiComponent::wifi_apply_hostname_() {
|
bool WiFiComponent::wifi_apply_hostname_() {
|
||||||
const std::string &hostname = App.get_name();
|
const auto &hostname = App.get_name();
|
||||||
bool ret = wifi_station_set_hostname(const_cast<char *>(hostname.c_str()));
|
bool ret = wifi_station_set_hostname(const_cast<char *>(hostname.c_str()));
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
ESP_LOGV(TAG, "Set hostname failed");
|
ESP_LOGV(TAG, "Set hostname failed");
|
||||||
|
|||||||
+32
-22
@@ -138,26 +138,36 @@ static constexpr uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for qu
|
|||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
void pre_setup(const std::string &name, const std::string &friendly_name, bool name_add_mac_suffix) {
|
#ifdef ESPHOME_NAME_ADD_MAC_SUFFIX
|
||||||
|
/// Pre-setup with MAC suffix: overwrites placeholder in mutable static buffers with actual MAC.
|
||||||
|
void pre_setup(char *name, size_t name_len, char *friendly_name, size_t friendly_name_len) {
|
||||||
arch_init();
|
arch_init();
|
||||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
this->name_add_mac_suffix_ = true;
|
||||||
if (name_add_mac_suffix) {
|
// MAC address length: 12 hex chars + null terminator
|
||||||
// MAC address length: 12 hex chars + null terminator
|
constexpr size_t mac_address_len = 13;
|
||||||
constexpr size_t mac_address_len = 13;
|
// MAC address suffix length (last 6 characters of 12-char MAC address string)
|
||||||
// MAC address suffix length (last 6 characters of 12-char MAC address string)
|
constexpr size_t mac_address_suffix_len = 6;
|
||||||
constexpr size_t mac_address_suffix_len = 6;
|
char mac_addr[mac_address_len];
|
||||||
char mac_addr[mac_address_len];
|
get_mac_address_into_buffer(mac_addr);
|
||||||
get_mac_address_into_buffer(mac_addr);
|
// Overwrite the placeholder suffix in the mutable static buffers with actual MAC
|
||||||
const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
|
// name is always non-empty (validated by validate_hostname in Python config)
|
||||||
this->name_ = make_name_with_suffix(name, '-', mac_suffix_ptr, mac_address_suffix_len);
|
memcpy(name + name_len - mac_address_suffix_len, mac_addr + mac_address_suffix_len, mac_address_suffix_len);
|
||||||
if (!friendly_name.empty()) {
|
if (friendly_name_len > 0) {
|
||||||
this->friendly_name_ = make_name_with_suffix(friendly_name, ' ', mac_suffix_ptr, mac_address_suffix_len);
|
memcpy(friendly_name + friendly_name_len - mac_address_suffix_len, mac_addr + mac_address_suffix_len,
|
||||||
}
|
mac_address_suffix_len);
|
||||||
} else {
|
|
||||||
this->name_ = name;
|
|
||||||
this->friendly_name_ = friendly_name;
|
|
||||||
}
|
}
|
||||||
|
this->name_ = StringRef(name, name_len);
|
||||||
|
this->friendly_name_ = StringRef(friendly_name, friendly_name_len);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
/// Pre-setup without MAC suffix: StringRef points directly at const string literals in flash.
|
||||||
|
void pre_setup(const char *name, size_t name_len, const char *friendly_name, size_t friendly_name_len) {
|
||||||
|
arch_init();
|
||||||
|
this->name_add_mac_suffix_ = false;
|
||||||
|
this->name_ = StringRef(name, name_len);
|
||||||
|
this->friendly_name_ = StringRef(friendly_name, friendly_name_len);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
void register_device(Device *device) { this->devices_.push_back(device); }
|
void register_device(Device *device) { this->devices_.push_back(device); }
|
||||||
@@ -274,10 +284,10 @@ class Application {
|
|||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
/// Get the name of this Application set by pre_setup().
|
/// Get the name of this Application set by pre_setup().
|
||||||
const std::string &get_name() const { return this->name_; }
|
const StringRef &get_name() const { return this->name_; }
|
||||||
|
|
||||||
/// Get the friendly name of this Application set by pre_setup().
|
/// Get the friendly name of this Application set by pre_setup().
|
||||||
const std::string &get_friendly_name() const { return this->friendly_name_; }
|
const StringRef &get_friendly_name() const { return this->friendly_name_; }
|
||||||
|
|
||||||
/// Get the area of this Application set by pre_setup().
|
/// Get the area of this Application set by pre_setup().
|
||||||
const char *get_area() const {
|
const char *get_area() const {
|
||||||
@@ -627,9 +637,9 @@ class Application {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// std::string members (typically 24-32 bytes each)
|
// StringRef members (8 bytes each: pointer + size)
|
||||||
std::string name_;
|
StringRef name_;
|
||||||
std::string friendly_name_;
|
StringRef friendly_name_;
|
||||||
|
|
||||||
// 4-byte members
|
// 4-byte members
|
||||||
uint32_t last_loop_{0};
|
uint32_t last_loop_{0};
|
||||||
|
|||||||
+55
-5
@@ -50,6 +50,7 @@ from esphome.core import (
|
|||||||
)
|
)
|
||||||
from esphome.helpers import (
|
from esphome.helpers import (
|
||||||
copy_file_if_changed,
|
copy_file_if_changed,
|
||||||
|
cpp_string_escape,
|
||||||
fnv1a_32bit_hash,
|
fnv1a_32bit_hash,
|
||||||
get_str_env,
|
get_str_env,
|
||||||
walk_files,
|
walk_files,
|
||||||
@@ -58,6 +59,38 @@ from esphome.types import ConfigType
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# C++ variable names and separators for app name buffers (used with MAC suffix)
|
||||||
|
_APP_NAME_BUF_VAR = "esphome_app_name_buf"
|
||||||
|
_APP_NAME_MAC_SEP = "-"
|
||||||
|
_APP_FRIENDLY_NAME_BUF_VAR = "esphome_app_friendly_name_buf"
|
||||||
|
_APP_FRIENDLY_NAME_MAC_SEP = " "
|
||||||
|
# Placeholder suffix for MAC address (last 6 hex chars)
|
||||||
|
_MAC_SUFFIX_PLACEHOLDER = "XXXXXX"
|
||||||
|
|
||||||
|
|
||||||
|
def make_app_name_cpp(
|
||||||
|
value: str, var_name: str, sep: str, *, add_mac_suffix: bool
|
||||||
|
) -> tuple[str, str | None, int]:
|
||||||
|
"""Compute C++ expression and optional global declaration for an app name.
|
||||||
|
|
||||||
|
Returns (cpp_expr, global_decl_or_none, byte_length).
|
||||||
|
- cpp_expr: The C++ expression to pass to pre_setup (var name or string literal).
|
||||||
|
- global_decl: A static char[] declaration string, or None if not needed.
|
||||||
|
- byte_length: The UTF-8 byte length of the string value.
|
||||||
|
"""
|
||||||
|
if add_mac_suffix:
|
||||||
|
buf_value = "" if not value else f"{value}{sep}{_MAC_SUFFIX_PLACEHOLDER}"
|
||||||
|
escaped = cpp_string_escape(buf_value)
|
||||||
|
return (
|
||||||
|
var_name,
|
||||||
|
f"static char {var_name}[] = {escaped};",
|
||||||
|
len(buf_value.encode("utf-8")),
|
||||||
|
)
|
||||||
|
if not value:
|
||||||
|
return '""', None, 0
|
||||||
|
return cpp_string_escape(value), None, len(value.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
StartupTrigger = cg.esphome_ns.class_(
|
StartupTrigger = cg.esphome_ns.class_(
|
||||||
"StartupTrigger", cg.Component, automation.Trigger.template()
|
"StartupTrigger", cg.Component, automation.Trigger.template()
|
||||||
)
|
)
|
||||||
@@ -78,6 +111,8 @@ VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"}
|
|||||||
|
|
||||||
def validate_hostname(config):
|
def validate_hostname(config):
|
||||||
# Keep in sync with ESPHOME_DEVICE_NAME_MAX_LEN in esphome/core/entity_base.h
|
# Keep in sync with ESPHOME_DEVICE_NAME_MAX_LEN in esphome/core/entity_base.h
|
||||||
|
if not config[CONF_NAME]:
|
||||||
|
raise cv.Invalid("Hostname must not be empty", path=[CONF_NAME])
|
||||||
max_length = 31
|
max_length = 31
|
||||||
if config[CONF_NAME_ADD_MAC_SUFFIX]:
|
if config[CONF_NAME_ADD_MAC_SUFFIX]:
|
||||||
max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used
|
max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used
|
||||||
@@ -555,13 +590,28 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
# Construct App via placement new — see application.cpp for storage details
|
# Construct App via placement new — see application.cpp for storage details
|
||||||
cg.add_global(cg.RawStatement("#include <new>"))
|
cg.add_global(cg.RawStatement("#include <new>"))
|
||||||
cg.add(cg.RawExpression("new (&App) Application()"))
|
cg.add(cg.RawExpression("new (&App) Application()"))
|
||||||
cg.add(
|
name = config[CONF_NAME]
|
||||||
cg.App.pre_setup(
|
friendly_name = config[CONF_FRIENDLY_NAME]
|
||||||
config[CONF_NAME],
|
name_add_mac_suffix = config[CONF_NAME_ADD_MAC_SUFFIX]
|
||||||
config[CONF_FRIENDLY_NAME],
|
|
||||||
config[CONF_NAME_ADD_MAC_SUFFIX],
|
def _emit_app_name(
|
||||||
|
value: str, var_name: str, sep: str
|
||||||
|
) -> tuple[cg.Expression, int]:
|
||||||
|
"""Emit codegen for an app name and return (expression, byte_length)."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
value, var_name, sep, add_mac_suffix=name_add_mac_suffix
|
||||||
)
|
)
|
||||||
|
if global_decl is not None:
|
||||||
|
cg.add_global(cg.RawStatement(global_decl))
|
||||||
|
return cg.RawExpression(cpp_expr), byte_len
|
||||||
|
|
||||||
|
name_expr, name_len = _emit_app_name(name, _APP_NAME_BUF_VAR, _APP_NAME_MAC_SEP)
|
||||||
|
friendly_expr, friendly_len = _emit_app_name(
|
||||||
|
friendly_name, _APP_FRIENDLY_NAME_BUF_VAR, _APP_FRIENDLY_NAME_MAC_SEP
|
||||||
)
|
)
|
||||||
|
if name_add_mac_suffix:
|
||||||
|
cg.add_define("ESPHOME_NAME_ADD_MAC_SUFFIX")
|
||||||
|
cg.add(cg.App.pre_setup(name_expr, name_len, friendly_expr, friendly_len))
|
||||||
# Define component count for static allocation
|
# Define component count for static allocation
|
||||||
cg.add_define("ESPHOME_COMPONENT_COUNT", len(CORE.component_ids))
|
cg.add_define("ESPHOME_COMPONENT_COUNT", len(CORE.component_ids))
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#define ESPHOME_PROJECT_VERSION "v2"
|
#define ESPHOME_PROJECT_VERSION "v2"
|
||||||
#define ESPHOME_PROJECT_VERSION_30 "v2"
|
#define ESPHOME_PROJECT_VERSION_30 "v2"
|
||||||
#define ESPHOME_VARIANT "ESP32"
|
#define ESPHOME_VARIANT "ESP32"
|
||||||
|
#define ESPHOME_NAME_ADD_MAC_SUFFIX
|
||||||
#define ESPHOME_DEBUG_SCHEDULER
|
#define ESPHOME_DEBUG_SCHEDULER
|
||||||
#define ESPHOME_DEBUG_API
|
#define ESPHOME_DEBUG_API
|
||||||
|
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ void EntityBase::set_name(const char *name, uint32_t object_id_hash) {
|
|||||||
// Bug-for-bug compatibility with OLD behavior:
|
// Bug-for-bug compatibility with OLD behavior:
|
||||||
// - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback)
|
// - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback)
|
||||||
// - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name
|
// - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name
|
||||||
const std::string &friendly = App.get_friendly_name();
|
const auto &friendly = App.get_friendly_name();
|
||||||
if (App.is_name_add_mac_suffix_enabled()) {
|
if (App.is_name_add_mac_suffix_enabled()) {
|
||||||
// MAC suffix enabled - use friendly_name directly (even if empty) for compatibility
|
// MAC suffix enabled - use friendly_name directly (even if empty) for compatibility
|
||||||
this->name_ = StringRef(friendly);
|
this->name_ = friendly;
|
||||||
} else {
|
} else {
|
||||||
// No MAC suffix - fallback to device name if friendly_name is empty
|
// No MAC suffix - fallback to device name if friendly_name is empty
|
||||||
this->name_ = StringRef(!friendly.empty() ? friendly : App.get_name());
|
this->name_ = !friendly.empty() ? friendly : App.get_name();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->flags_.has_own_name = false;
|
this->flags_.has_own_name = false;
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
using namespace esphome;
|
using namespace esphome;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
App.pre_setup("livingroom", "LivingRoom", false);
|
static char name[] = "livingroom";
|
||||||
|
static char friendly_name[] = "LivingRoom";
|
||||||
|
App.pre_setup(name, sizeof(name) - 1, friendly_name, sizeof(friendly_name) - 1);
|
||||||
auto *log = new logger::Logger(115200); // NOLINT
|
auto *log = new logger::Logger(115200); // NOLINT
|
||||||
log->pre_setup();
|
log->pre_setup();
|
||||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from esphome.const import (
|
|||||||
from esphome.core import CORE, config
|
from esphome.core import CORE, config
|
||||||
from esphome.core.config import (
|
from esphome.core.config import (
|
||||||
Area,
|
Area,
|
||||||
|
make_app_name_cpp,
|
||||||
preload_core_config,
|
preload_core_config,
|
||||||
valid_include,
|
valid_include,
|
||||||
valid_project_name,
|
valid_project_name,
|
||||||
@@ -969,3 +970,79 @@ def test_config_hash_different_for_different_configs() -> None:
|
|||||||
hash2 = CORE.config_hash
|
hash2 = CORE.config_hash
|
||||||
|
|
||||||
assert hash1 != hash2
|
assert hash1 != hash2
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_no_mac_simple() -> None:
|
||||||
|
"""Test simple name without MAC suffix returns string literal."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"my-device", "buf", "-", add_mac_suffix=False
|
||||||
|
)
|
||||||
|
assert cpp_expr == '"my-device"'
|
||||||
|
assert global_decl is None
|
||||||
|
assert byte_len == 9
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_no_mac_empty() -> None:
|
||||||
|
"""Test empty name without MAC suffix."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"", "buf", "-", add_mac_suffix=False
|
||||||
|
)
|
||||||
|
assert cpp_expr == '""'
|
||||||
|
assert global_decl is None
|
||||||
|
assert byte_len == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_mac_suffix() -> None:
|
||||||
|
"""Test name with MAC suffix emits static buffer."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"my-device", "esphome_app_name_buf", "-", add_mac_suffix=True
|
||||||
|
)
|
||||||
|
assert cpp_expr == "esphome_app_name_buf"
|
||||||
|
assert global_decl is not None
|
||||||
|
assert "static char esphome_app_name_buf[]" in global_decl
|
||||||
|
assert "my-device-XXXXXX" in global_decl
|
||||||
|
assert byte_len == len("my-device-XXXXXX")
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_mac_suffix_empty() -> None:
|
||||||
|
"""Test empty name with MAC suffix emits empty static buffer."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"", "esphome_app_name_buf", "-", add_mac_suffix=True
|
||||||
|
)
|
||||||
|
assert cpp_expr == "esphome_app_name_buf"
|
||||||
|
assert global_decl is not None
|
||||||
|
assert "static char esphome_app_name_buf[]" in global_decl
|
||||||
|
assert byte_len == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_mac_suffix_space_sep() -> None:
|
||||||
|
"""Test friendly name uses space separator for MAC suffix."""
|
||||||
|
cpp_expr, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"My Device", "esphome_app_friendly_name_buf", " ", add_mac_suffix=True
|
||||||
|
)
|
||||||
|
assert cpp_expr == "esphome_app_friendly_name_buf"
|
||||||
|
assert global_decl is not None
|
||||||
|
assert "My Device XXXXXX" in global_decl
|
||||||
|
assert byte_len == len("My Device XXXXXX")
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_non_ascii_utf8_length() -> None:
|
||||||
|
"""Test non-ASCII characters use UTF-8 byte length."""
|
||||||
|
_, global_decl, byte_len = make_app_name_cpp(
|
||||||
|
"café", "buf", "-", add_mac_suffix=False
|
||||||
|
)
|
||||||
|
assert byte_len == len("café".encode()) # 5 bytes, not 4 chars
|
||||||
|
assert global_decl is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_non_ascii_mac_suffix_utf8_length() -> None:
|
||||||
|
"""Test non-ASCII with MAC suffix uses UTF-8 byte length."""
|
||||||
|
_, _, byte_len = make_app_name_cpp("café", "buf", "-", add_mac_suffix=True)
|
||||||
|
assert byte_len == len("café-XXXXXX".encode())
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_app_name_cpp_special_chars_escaped() -> None:
|
||||||
|
"""Test special characters are properly escaped in C++ string."""
|
||||||
|
cpp_expr, _, _ = make_app_name_cpp('my "device"', "buf", "-", add_mac_suffix=False)
|
||||||
|
# cpp_string_escape uses octal escapes for quotes
|
||||||
|
assert '"' not in cpp_expr[1:-1] # no unescaped quotes inside the outer quotes
|
||||||
|
|||||||
Reference in New Issue
Block a user