From abcd89cd1ff8eaef953c9cc397a368f31ed36cdc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Feb 2026 16:06:28 -0600 Subject: [PATCH] [wifi] Avoid .rodata RAM cost for ManualIP on ESP8266 On ESP8266, .rodata is mapped to DRAM (RAM), not flash. When StructInitializer is used with all compile-time constant fields, the compiler places the entire struct as a const blob in .rodata, silently consuming ~20 bytes of RAM. Switch to field-by-field assignment on ESP8266 so the IP address values are encoded as immediate operands in flash instructions instead of a .rodata blob. Other platforms continue to use the aggregate initializer since their .rodata is flash-mapped. --- esphome/components/wifi/__init__.py | 22 ++++++++++++++++----- tests/components/wifi/test.esp8266-ard.yaml | 6 ++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 2aa63b87cc..3f0b61b60c 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -439,13 +439,25 @@ def safe_ip(ip): def manual_ip(config): if config is None: return None + fields = { + "static_ip": CONF_STATIC_IP, + "gateway": CONF_GATEWAY, + "subnet": CONF_SUBNET, + "dns1": CONF_DNS1, + "dns2": CONF_DNS2, + } + if CORE.is_esp8266: + # On ESP8266, .rodata is mapped to RAM. Using StructInitializer with all + # compile-time constant fields causes the compiler to place a const blob + # in .rodata, silently consuming ~20 bytes of RAM. Field-by-field assignment + # encodes the values as immediate operands in flash instructions instead. + cg.add(cg.RawExpression("wifi::ManualIP manual_ip{}")) + for member, key in fields.items(): + cg.add(cg.RawExpression(f"manual_ip.{member} = {safe_ip(config.get(key))}")) + return cg.RawExpression("manual_ip") 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))), + *((member, safe_ip(config.get(key))) for member, key in fields.items()), ) diff --git a/tests/components/wifi/test.esp8266-ard.yaml b/tests/components/wifi/test.esp8266-ard.yaml index 709a639ad6..034b74b3fb 100644 --- a/tests/components/wifi/test.esp8266-ard.yaml +++ b/tests/components/wifi/test.esp8266-ard.yaml @@ -1,6 +1,12 @@ wifi: min_auth_mode: WPA2 post_connect_roaming: true + manual_ip: + static_ip: 192.168.1.100 + gateway: 192.168.1.1 + subnet: 255.255.255.0 + dns1: 1.1.1.1 + dns2: 8.8.8.8 packages: - !include common.yaml