diff --git a/esphome/__main__.py b/esphome/__main__.py index b8e1055b703..5a58abcc781 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -519,43 +519,43 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int: if rc != 0: return rc - # Check if firmware was rebuilt and emit buildinfo + create manifest - _check_and_emit_buildinfo() + # Check if firmware was rebuilt and emit build_info + create manifest + _check_and_emit_build_info() idedata = platformio_api.get_idedata(config) return 0 if idedata is not None else 1 -def _check_and_emit_buildinfo() -> None: - """Check if firmware was rebuilt and emit buildinfo.""" +def _check_and_emit_build_info() -> None: + """Check if firmware was rebuilt and emit build_info.""" import json firmware_path = CORE.firmware_bin - buildinfo_json_path = CORE.relative_build_path("buildinfo.json") + build_info_json_path = CORE.relative_build_path("build_info.json") # Check if both files exist - if not firmware_path.exists() or not buildinfo_json_path.exists(): + if not firmware_path.exists() or not build_info_json_path.exists(): return - # Check if firmware is newer than buildinfo (indicating a relink occurred) - if firmware_path.stat().st_mtime <= buildinfo_json_path.stat().st_mtime: + # Check if firmware is newer than build_info (indicating a relink occurred) + if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime: return - # Read buildinfo from JSON + # Read build_info from JSON try: - with open(buildinfo_json_path, encoding="utf-8") as f: - buildinfo = json.load(f) + with open(build_info_json_path, encoding="utf-8") as f: + build_info = json.load(f) except (OSError, json.JSONDecodeError) as e: - _LOGGER.debug("Failed to read buildinfo: %s", e) + _LOGGER.debug("Failed to read build_info: %s", e) return - config_hash = buildinfo.get("config_hash") - build_time = buildinfo.get("build_time") + config_hash = build_info.get("config_hash") + build_time = build_info.get("build_time") if config_hash is None or build_time is None: return - # Emit buildinfo + # Emit build_info _LOGGER.info( "Build Info: config_hash=0x%08x build_time=%s", config_hash, build_time ) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5186e5afdab..992cae028c7 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -19,6 +19,7 @@ #endif #include "esphome/components/network/util.h" #include "esphome/core/application.h" +#include "esphome/core/build_info.h" #include "esphome/core/entity_base.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -1472,7 +1473,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) { resp.set_esphome_version(ESPHOME_VERSION_REF); - resp.set_compilation_time(App.get_compilation_time_ref()); + // Stack buffer for build time string + char build_time_str[BUILD_TIME_STR_SIZE]; + get_build_time_string(build_time_str); + resp.set_compilation_time(StringRef(build_time_str)); // Manufacturer string - define once, handle ESP8266 PROGMEM separately #if defined(USE_ESP8266) || defined(USE_ESP32) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 5d2bedae790..f06013fb7e8 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -2,7 +2,7 @@ #ifdef USE_MQTT -#include "esphome/core/application.h" +#include "esphome/core/build_info.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/version.h" @@ -154,7 +154,9 @@ bool MQTTComponent::send_discovery_() { device_info[MQTT_DEVICE_MANUFACTURER] = model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME); #else - device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_VERSION " (" + App.get_compilation_time_ref() + ")"; + char build_time_str[BUILD_TIME_STR_SIZE]; + get_build_time_string(build_time_str); + device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(ESPHOME_VERSION " (%s)", build_time_str); device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; #if defined(USE_ESP8266) || defined(USE_ESP32) device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif"; diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index ffb9e2bc020..01e7b761019 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -1,4 +1,5 @@ #include "sen5x.h" +#include "esphome/core/build_info.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -154,10 +155,10 @@ void SEN5XComponent::setup() { if (this->voc_sensor_ && this->store_baseline_) { uint32_t combined_serial = encode_uint24(this->serial_number_[0], this->serial_number_[1], this->serial_number_[2]); - // Hash with compilation time and serial number + // Hash with build time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(combined_serial)); + uint32_t hash = static_cast(get_build_time()) ^ combined_serial; this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index fa548ce94eb..5174281ad52 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,5 +1,5 @@ #include "sgp30.h" -#include "esphome/core/application.h" +#include "esphome/core/build_info.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -72,10 +72,10 @@ void SGP30Component::setup() { return; } - // Hash with compilation time and serial number + // Hash with build time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + uint32_t hash = static_cast(get_build_time()) ^ static_cast(this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index a0c957d608e..ec54e6d8f68 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -1,4 +1,5 @@ #include "sgp4x.h" +#include "esphome/core/build_info.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" #include @@ -56,10 +57,10 @@ void SGP4xComponent::setup() { ESP_LOGD(TAG, "Version 0x%0X", featureset); if (this->store_baseline_) { - // Hash with compilation time and serial number + // Hash with build time and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so mulitple sensors can be used without conflict - uint32_t hash = fnv1_hash(App.get_compilation_time_ref() + std::to_string(this->serial_number_)); + uint32_t hash = static_cast(get_build_time()) ^ static_cast(this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 78d0fb501ba..1b9bde7f81c 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -1,6 +1,6 @@ #include "version_text_sensor.h" +#include "esphome/core/build_info.h" #include "esphome/core/log.h" -#include "esphome/core/application.h" #include "esphome/core/version.h" #include "esphome/core/helpers.h" @@ -13,7 +13,9 @@ void VersionTextSensor::setup() { if (this->hide_timestamp_) { this->publish_state(ESPHOME_VERSION); } else { - this->publish_state(str_sprintf(ESPHOME_VERSION " %s", App.get_compilation_time_ref().c_str())); + char build_time_str[BUILD_TIME_STR_SIZE]; + get_build_time_string(build_time_str); + this->publish_state(str_sprintf(ESPHOME_VERSION " %s", build_time_str)); } } float VersionTextSensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d46916bfd93..fbc5fefb00d 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -2,6 +2,7 @@ #ifdef USE_WIFI #include #include +#include "esphome/core/build_info.h" #ifdef USE_ESP32 #if (ESP_IDF_VERSION_MAJOR >= 5 && ESP_IDF_VERSION_MINOR >= 1) @@ -360,7 +361,7 @@ void WiFiComponent::start() { get_mac_address_pretty_into_buffer(mac_s)); this->last_connected_ = millis(); - uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time_ref().c_str()) : 88491487UL; + uint32_t hash = this->has_sta() ? static_cast(get_build_time()) : 88491487UL; this->pref_ = global_preferences->make_preference(hash, true); #ifdef USE_WIFI_FAST_CONNECT diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a85d671a070..2fee5a6fe85 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -1,4 +1,5 @@ #include "esphome/core/application.h" +#include "esphome/core/build_info.h" #include "esphome/core/log.h" #include "esphome/core/version.h" #include "esphome/core/hal.h" @@ -191,7 +192,9 @@ void Application::loop() { if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { - ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_); + char build_time_str[BUILD_TIME_STR_SIZE]; + get_build_time_string(build_time_str); + ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str); #ifdef ESPHOME_PROJECT_NAME ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION); #endif diff --git a/esphome/core/application.h b/esphome/core/application.h index 8e2035b7c5e..09cd7e5bbc0 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -101,7 +101,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick class Application { public: void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, - const char *compilation_time, bool name_add_mac_suffix) { + bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { @@ -121,7 +121,6 @@ class Application { this->friendly_name_ = friendly_name; } this->comment_ = comment; - this->compilation_time_ = compilation_time; } #ifdef USE_DEVICES @@ -261,10 +260,6 @@ class Application { bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } - std::string get_compilation_time() const { return this->compilation_time_; } - /// Get the compilation time as StringRef (for API usage) - StringRef get_compilation_time_ref() const { return StringRef(this->compilation_time_); } - /// Get the cached time in milliseconds from when the current component started its loop execution inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; } @@ -478,7 +473,6 @@ class Application { // Pointer-sized members first Component *current_component_{nullptr}; const char *comment_{nullptr}; - const char *compilation_time_{nullptr}; // std::vector (3 pointers each: begin, end, capacity) // Partitioned vector design for looping components diff --git a/esphome/core/build_info.cpp b/esphome/core/build_info.cpp new file mode 100644 index 00000000000..85d07ce0203 --- /dev/null +++ b/esphome/core/build_info.cpp @@ -0,0 +1,24 @@ +#include "build_info.h" +#include "build_info_data.h" +#include + +#ifdef USE_ESP8266 +#include +#endif + +namespace esphome { + +uint32_t get_config_hash() { return ESPHOME_CONFIG_HASH; } + +time_t get_build_time() { return ESPHOME_BUILD_TIME; } + +void get_build_time_string(std::span buffer) { +#ifdef USE_ESP8266 + strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size()); +#else + strncpy(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size()); +#endif + buffer[buffer.size() - 1] = '\0'; +} + +} // namespace esphome diff --git a/esphome/core/build_info.h b/esphome/core/build_info.h new file mode 100644 index 00000000000..6a427ea5116 --- /dev/null +++ b/esphome/core/build_info.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include + +namespace esphome { + +/// Size of buffer required for build time string (including null terminator) +static constexpr size_t BUILD_TIME_STR_SIZE = 24; + +/// Get the config hash as a 32-bit integer +uint32_t get_config_hash(); + +/// Get the build time as a Unix timestamp +time_t get_build_time(); + +/// Copy the build time string into the provided buffer +/// Buffer must be BUILD_TIME_STR_SIZE bytes (compile-time enforced) +void get_build_time_string(std::span buffer); + +} // namespace esphome diff --git a/esphome/core/build_info_data.h b/esphome/core/build_info_data.h new file mode 100644 index 00000000000..e81645a3da2 --- /dev/null +++ b/esphome/core/build_info_data.h @@ -0,0 +1,10 @@ +#pragma once + +// This file is not used by the runtime, instead, a version is generated during +// compilation with the actual build info values. +// +// This file is only used by static analyzers and IDEs. + +#define ESPHOME_CONFIG_HASH 0x12345678U +#define ESPHOME_BUILD_TIME 1700000000 +static const char ESPHOME_BUILD_TIME_STR[] = "Jan 01 2024, 00:00:00"; diff --git a/esphome/core/buildinfo.cpp b/esphome/core/buildinfo.cpp deleted file mode 100644 index edfff44b187..00000000000 --- a/esphome/core/buildinfo.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - -// Build information is passed in via symbols defined in a linker script -// as that is the simplest way to include build timestamps without the -// changed timestamp itself causing a rebuild through dependencies, as -// it would if it were in a header file like version.h. -// -// It's passed in in *string* form so that it can go directly into the -// flash as .rodata instead of using precious RAM to build a date string -// from a time_t at runtime. -// -// Determining the target endianness and word size from the generation -// side is problematic, so it emits *four* sets of symbols into the -// linker script, for each of little-endian and big-endiand, 32-bit and -// 64-bit targets. -// -// The LINKERSYM macro gymnastics select the correct symbol for the -// target, named e.g. 'ESPHOME_BUILD_TIME_STR_32LE_0'. - -// Not all targets have (e.g. LibreTiny on BK72xx). -// Use the compiler built-in macros but defensively default to -// little-endian and 32-bit. -#if !defined(__BYTE_ORDER__) || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define BO LE -#else -#define BO BE -#endif - -#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8 -#define WS 64 // NOLINT -#else -#define WS 32 // NOLINT -#define USE_32BIT -#endif - -// If you have to ask, you don't want to know... -#define LINKERSYM2(name, ws, bo, us, num) ESPHOME_##name##_##ws##bo##us##num -#define LINKERSYM1(name, ws, bo, us, num) LINKERSYM2(name, ws, bo, us, num) -#define LINKERSYM(name, num) LINKERSYM1(name, WS, BO, _, num) - -extern "C" { -extern const char ESPHOME_BUILD_TIME[]; -extern const char LINKERSYM(CONFIG_HASH_STR, 0)[]; -extern const char LINKERSYM(CONFIG_HASH_STR, 1)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 0)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 1)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 2)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 3)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 4)[]; -extern const char LINKERSYM(BUILD_TIME_STR, 5)[]; -} - -namespace esphome::buildinfo { - -// An 8-byte string plus terminating NUL. -struct ConfigHashStruct { - uintptr_t data0; -#ifdef USE_32BIT - uintptr_t data1; -#endif - char nul; -} __attribute__((packed)); - -extern const ConfigHashStruct CONFIG_HASH_STR = {(uintptr_t) &LINKERSYM(CONFIG_HASH_STR, 0), -#ifdef USE_32BIT - (uintptr_t) &LINKERSYM(CONFIG_HASH_STR, 1), -#endif - 0}; - -// A 21-byte string plus terminating NUL, in 24 bytes -extern const uintptr_t BUILD_TIME_STR[] = { - (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 0), (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 1), - (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 2), -#ifdef USE_32BIT - (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 3), (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 4), - (uintptr_t) &LINKERSYM(BUILD_TIME_STR, 5), -#endif -}; - -extern const uintptr_t BUILD_TIME = (uintptr_t) &ESPHOME_BUILD_TIME; - -} // namespace esphome::buildinfo diff --git a/esphome/core/buildinfo.h b/esphome/core/buildinfo.h deleted file mode 100644 index d86b2af0673..00000000000 --- a/esphome/core/buildinfo.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include -#include - -// Build information functions that provide config hash and build time. -// The actual values are provided by linker-defined symbols to avoid -// unnecessary rebuilds when only the build time changes. -// This is kept in its own file so that only files that need build-specific -// information have to include it explicitly. - -namespace esphome::buildinfo { - -extern const char CONFIG_HASH_STR[]; -extern const char BUILD_TIME_STR[]; -extern const uintptr_t BUILD_TIME; - -static inline const char *get_config_hash() { return CONFIG_HASH_STR; } - -static inline time_t get_build_time() { return (time_t) BUILD_TIME; } - -static inline const char *get_build_time_string() { return BUILD_TIME_STR; } - -} // namespace esphome::buildinfo diff --git a/esphome/core/buildinfo.py.script b/esphome/core/buildinfo.py.script deleted file mode 100644 index 9a4da86537e..00000000000 --- a/esphome/core/buildinfo.py.script +++ /dev/null @@ -1,2 +0,0 @@ -Import("env") # noqa: F821 -env.Append(LINKFLAGS=["buildinfo.ld"]) # noqa: F821 diff --git a/esphome/core/config.py b/esphome/core/config.py index f7a53051442..97157b6f929 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -501,7 +501,6 @@ async def to_code(config: ConfigType) -> None: config[CONF_NAME], config[CONF_FRIENDLY_NAME], config.get(CONF_COMMENT, ""), - cg.RawExpression("esphome::buildinfo::get_build_time_string()"), config[CONF_NAME_ADD_MAC_SUFFIX], ) ) diff --git a/esphome/writer.py b/esphome/writer.py index 96dcbf3860a..5960dc0af52 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -7,7 +7,6 @@ from pathlib import Path import re import shutil import stat -import struct import time from types import TracebackType @@ -250,21 +249,16 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) - # Write buildinfo linker script, JSON metadata, and copy the PlatformIO script - config_hash, build_time, build_time_str = get_buildinfo() - write_file( - CORE.relative_build_path("buildinfo.ld"), - generate_buildinfo_ld(config_hash, build_time, build_time_str), + # Write build_info header and JSON metadata + config_hash, build_time, build_time_str = get_build_info() + write_file_if_changed( + CORE.relative_src_path("esphome", "core", "build_info_data.h"), + generate_build_info_data_h(config_hash, build_time, build_time_str), ) write_file( - CORE.relative_build_path("buildinfo.json"), + CORE.relative_build_path("build_info.json"), json.dumps({"config_hash": config_hash, "build_time": build_time}), ) - copy_file_if_changed( - Path(__file__).parent / "core" / "buildinfo.py.script", - CORE.relative_build_path("buildinfo.py"), - ) - CORE.add_platformio_option("extra_scripts", ["pre:buildinfo.py"]) platform = "esphome.components." + CORE.target_platform try: @@ -290,33 +284,8 @@ def generate_version_h(): ) -def _encode_string_symbols( - text: str, prefix: str, bits: int, bit_suffix: str, endian: str, endian_suffix: str -) -> list[str]: - """Encode a string as linker symbols for given word size and endianness.""" - symbols: list[str] = [] - # Pad to word boundary with NUL (build time strings need trailing NUL) - padded = text if prefix == "CONFIG_HASH_STR" else text + "\0" - while len(padded) % bits != 0: - padded += "\0" - - for i in range(0, len(padded), bits): - chunk = padded[i : i + bits].encode("utf-8") - if bits == 8: - value = struct.unpack(endian + "Q", chunk)[0] - symbols.append( - f"ESPHOME_{prefix}_{bit_suffix}{endian_suffix}_{i // bits} = 0x{value:016x};" - ) - else: - value = struct.unpack(endian + "I", chunk)[0] - symbols.append( - f"ESPHOME_{prefix}_{bit_suffix}{endian_suffix}_{i // bits} = 0x{value:08x};" - ) - return symbols - - -def get_buildinfo() -> tuple[int, int, str]: - """Calculate buildinfo values from current config. +def get_build_info() -> tuple[int, int, str]: + """Calculate build_info values from current config. Returns: Tuple of (config_hash, build_time, build_time_str) @@ -331,42 +300,20 @@ def get_buildinfo() -> tuple[int, int, str]: return config_hash, build_time, build_time_str -def generate_buildinfo_ld( +def generate_build_info_data_h( config_hash: int, build_time: int, build_time_str: str ) -> str: - """Generate buildinfo linker script with config hash and build time.""" - config_hash_str = f"{config_hash:08x}" - - # Generate symbols for all 4 variants: 32LE, 32BE, 64LE, 64BE - all_variants: list[str] = [] - - for bits, bit_suffix in [(4, "32"), (8, "64")]: - for endian, endian_suffix in [("<", "LE"), (">", "BE")]: - all_variants.extend( - _encode_string_symbols( - config_hash_str, - "CONFIG_HASH_STR", - bits, - bit_suffix, - endian, - endian_suffix, - ) - ) - all_variants.extend( - _encode_string_symbols( - build_time_str, - "BUILD_TIME_STR", - bits, - bit_suffix, - endian, - endian_suffix, - ) - ) - - return f"""/* Auto-generated buildinfo symbols */ -ESPHOME_BUILD_TIME = {build_time}; -ESPHOME_CONFIG_HASH = 0x{config_hash:08x}; -{chr(10).join(all_variants)} + """Generate build_info_data.h header with config hash and build time.""" + return f"""#pragma once +// Auto-generated build_info data +#define ESPHOME_CONFIG_HASH 0x{config_hash:08x}U +#define ESPHOME_BUILD_TIME {build_time} +#ifdef USE_ESP8266 +#include +static const char ESPHOME_BUILD_TIME_STR[] PROGMEM = "{build_time_str}"; +#else +static const char ESPHOME_BUILD_TIME_STR[] = "{build_time_str}"; +#endif """