#!/usr/bin/env python # # Pre-build script: gzip webui/index.html into a PROGMEM C header so the # webconfig portal can serve the page straight from flash with # Content-Encoding: gzip. The generated header is tracked so ESP32 targets # that replace extra_scripts still compile from a clean checkout; this script # refreshes it whenever the source page (or this script) is newer. # # Output: src/helpers/esp32/WebConfigHtml.h # WEBCONFIG_HTML_GZ[] - gzipped page (PROGMEM) # WEBCONFIG_HTML_GZ_LEN - byte length # WEBCONFIG_HTML_ETAG - quoted strong ETag (sha256 prefix of the gz body) import gzip import hashlib import os import sys # PlatformIO/SCons injects Import(). Keep the generator directly runnable as # well so maintainers can refresh and verify the tracked header without first # starting a firmware build. try: Import("env") # noqa: F821 except NameError: pass SOURCE = os.path.join("webui", "index.html") OUTPUT = os.path.join("src", "helpers", "esp32", "WebConfigHtml.h") # __file__ is not defined inside PIO/SCons-executed extra_scripts SCRIPT = os.path.join("scripts", "generate_webconfig_html.py") def status(msg): sys.stderr.write("WebConfig HTML: %s\n" % msg) def needs_rebuild(): if not os.path.isfile(OUTPUT): return True out_mtime = os.path.getmtime(OUTPUT) if os.path.getmtime(SOURCE) > out_mtime: return True if os.path.isfile(SCRIPT) and os.path.getmtime(SCRIPT) > out_mtime: return True return False def main(): if not os.path.isfile(SOURCE): status("ERROR: %s not found" % SOURCE) sys.exit(2) if not needs_rebuild(): return with open(SOURCE, "rb") as f: raw = f.read() # mtime=0 keeps the gzip output (and therefore the ETag) deterministic gz = gzip.compress(raw, compresslevel=9, mtime=0) etag = hashlib.sha256(gz).hexdigest()[:16] lines = [] lines.append("// Auto-generated by scripts/generate_webconfig_html.py from %s" % SOURCE.replace(os.sep, "/")) lines.append("// DO NOT EDIT - edit webui/index.html instead.") lines.append("#pragma once") lines.append("#include ") lines.append("#include ") lines.append("") lines.append("const uint32_t WEBCONFIG_HTML_GZ_LEN = %d;" % len(gz)) lines.append('const char WEBCONFIG_HTML_ETAG[] = "\\"%s\\"";' % etag) lines.append("const uint8_t WEBCONFIG_HTML_GZ[] PROGMEM = {") for i in range(0, len(gz), 16): chunk = gz[i:i + 16] lines.append(" " + "".join("0x%02x," % b for b in chunk)) lines.append("};") lines.append("") os.makedirs(os.path.dirname(OUTPUT), exist_ok=True) with open(OUTPUT, "w") as f: f.write("\n".join(lines)) status("%s -> %s (%d bytes raw, %d bytes gzipped)" % (SOURCE, OUTPUT, len(raw), len(gz))) # This script is attached only to esp32_base. Generate unconditionally here: # CPPDEFINES is not fully resolved when PlatformIO loads pre-scripts, so trying # to detect the final role at this point can leave a stale embedded page. # Non-WebConfig ESP32 builds do not compile the generated data into firmware. main()