Files
firmware/extra_scripts/esp32_pre.py

170 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import json
import sys
from os.path import isfile
Import("env")
# From https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py
def _parse_size(value):
if isinstance(value, int):
return value
elif value.isdigit():
return int(value)
elif value.startswith("0x"):
return int(value, 16)
elif value[-1].upper() in ("K", "M"):
base = 1024 if value[-1].upper() == "K" else 1024 * 1024
return int(value[:-1]) * base
return value
def _parse_partitions(env):
partitions_csv = env.subst("$PARTITIONS_TABLE_CSV")
if not isfile(partitions_csv):
sys.stderr.write(
"Could not find the file %s with partitions " "table.\n" % partitions_csv
)
env.Exit(1)
return
result = []
# The first offset is 0x9000 because partition table is flashed to 0x8000 and
# occupies an entire flash sector, which size is 0x1000
next_offset = 0x9000
with open(partitions_csv) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
tokens = [t.strip() for t in line.split(",")]
if len(tokens) < 5:
continue
bound = 0x10000 if tokens[1] in ("0", "app") else 4
calculated_offset = (next_offset + bound - 1) & ~(bound - 1)
partition = {
"name": tokens[0],
"type": tokens[1],
"subtype": tokens[2],
"offset": tokens[3] or calculated_offset,
"size": tokens[4],
"flags": tokens[5] if len(tokens) > 5 else None,
}
result.append(partition)
next_offset = _parse_size(partition["offset"]) + _parse_size(
partition["size"]
)
return result
def mtjson_esp32_part(target, source, env):
part = _parse_partitions(env)
pj = json.dumps(part)
# print(f"JSON_PARTITIONS: {pj}")
# Dump json string to 'custom_mtjson_part' variable to use later when writing the manifest
env.Replace(custom_mtjson_part=pj)
env.AddPreAction("mtjson", mtjson_esp32_part)
# ---------------------------------------------------------------------------
# HybridCompile cache-poisoning workaround (pioarduino platform-espressif32)
#
# The platform decides whether the precompiled Arduino IDF libs can be reused
# by hashing only the custom_sdkconfig text + mcu
# (builder/frameworks/arduino.py::matching_custom_sdkconfig). The board JSON's
# PSRAM configuration (BOARD_HAS_PSRAM / memory_type) is not part of that
# hash, and nearly all our S3 variants share esp32s3_base's identical
# custom_sdkconfig. Building a PSRAM env (e.g. heltec-v4) followed by a
# no-PSRAM env (e.g. heltec-v3) therefore skips the framework-libs recompile
# and links CONFIG_SPIRAM=y libs into the no-PSRAM firmware, which boot-loops
# with "quad_psram: PSRAM chip is not connected".
#
# Workaround: before the platform's arduino.py SConscript reads the option,
# append a comment line derived from the board's PSRAM/memory configuration to
# this env's custom_sdkconfig. The comment is inert in kconfig but changes the
# md5, forcing the IDF-libs recompile whenever the PSRAM class changes. The
# derivation mirrors espidf.py::generate_board_specific_config().
#
# The marker must stay lowercase: arduino.py greps custom_sdkconfig for the
# substrings "PSRAM" and "CONFIG_SPIRAM=y" (has_psram_config) and would
# otherwise treat every board as having PSRAM.
# ---------------------------------------------------------------------------
SDKCONFIG_CACHE_KEY = "meshtastic_hybridcompile_cache_key"
def spiram_cache_class(env):
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32")
# memory_type: platformio.ini override > build.arduino.memory_type > build.memory_type
memory_type = None
try:
memory_type = env.GetProjectOption("board_build.memory_type", None)
except Exception:
pass
if not memory_type:
build_section = board.get("build", {})
memory_type = build_section.get("arduino", {}).get(
"memory_type"
) or build_section.get("memory_type")
extra_flags = board.get("build.extra_flags", "")
if isinstance(extra_flags, str):
has_psram = "PSRAM" in extra_flags
else:
has_psram = any("PSRAM" in str(flag) for flag in extra_flags)
if not has_psram:
if memory_type and (
"opi" in memory_type.lower() or "psram" in memory_type.lower()
):
has_psram = True
elif "psram_type" in board.get("build", {}):
has_psram = True
if has_psram:
psram_type = None
try:
psram_type = env.GetProjectOption("board_build.psram_type", None)
except Exception:
pass
if not psram_type and memory_type and len(memory_type.split("_")) == 2:
psram_type = memory_type.split("_")[1]
if not psram_type:
psram_type = board.get("build.psram_type", "") or (
"hex" if mcu == "esp32p4" else "qio"
)
psram_type = psram_type.lower()
if psram_type == "opi" and mcu == "esp32s3":
spiram = "oct"
elif psram_type == "hex":
spiram = "hex"
else:
spiram = "quad"
else:
spiram = "none"
return "memory_type=%s spiram=%s" % ((memory_type or "default").lower(), spiram)
def tag_sdkconfig_cache_key(env):
config = env.GetProjectConfig()
section = "env:" + env["PIOENV"]
if not config.has_option(section, "custom_sdkconfig"):
return
current = env.GetProjectOption("custom_sdkconfig")
if SDKCONFIG_CACHE_KEY in current:
return
marker = "# %s: %s" % (SDKCONFIG_CACHE_KEY, spiram_cache_class(env))
config.set(section, "custom_sdkconfig", current.rstrip("\n") + "\n" + marker)
tag_sdkconfig_cache_key(env)