Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/platformio_release.ini.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
; Do NOT commit a generated platformio_release.ini (it's in .gitignore).
; ----------------------------------------------------------------------------

[env]
extra_scripts = ${scripts_defaults.extra_scripts}
post:pio-scripts/factory_bin.py

[platformio]
default_envs = nodemcuv2
esp8266_2m
Expand Down
69 changes: 69 additions & 0 deletions pio-scripts/factory_bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Import("env")
import csv
import json
import os
import subprocess

OUTPUT_DIR = "build_output{}".format(os.path.sep)

def _get_cpp_define_value(env, define):
define_list = [item[-1] for item in env["CPPDEFINES"] if item[0] == define]
if define_list:
return define_list[0]
return None

def _get_app_offset(env):
try:
with open(env.BoardConfig().get("build.partitions")) as f:
for row in csv.reader(f):
row = [c.strip() for c in row]
if len(row) >= 4 and row[0] == "app0":
return int(row[3], 16)
except Exception:
pass
return 0x10000

def create_factory_bin(source, target, env):
if env["PIOPLATFORM"] == "espressif8266":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to run this gate the other way around (ie. if env["PIOPLATFORM"] != "espressif32": return).

If we add support for any other platforms in the future, they'll almost certainly need a different approach.

return

app_bin = os.path.normpath(str(target[0]))
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
flash_mode = env["__get_board_flash_mode"](env)
flash_freq = env["__get_board_f_flash"](env)
Comment on lines +33 to +34

@coderabbitai coderabbitai Bot Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Are "__get_board_flash_mode" and "__get_board_f_flash" documented/stable PlatformIO SCons environment functions?

💡 Result:

The functions get_board_flash_mode and get_board_f_flash are not documented, stable, or public PlatformIO API functions [1][2]. They are internal helper functions defined within specific PlatformIO development platforms, most notably the espressif32 platform [3][4][5][6]. These functions are injected into the SCons construction environment (env) by platform-specific builder scripts to facilitate the dynamic resolution of flash configuration parameters (like flash mode and flash frequency) from board configuration files during the build process [4][5][6]. Because they are implementation details of the platform's build logic, they are subject to change without notice and are not intended for use in user-level advanced scripts [1][2]. Attempting to use these functions in your own scripts can lead to runtime errors, particularly if the expected board configuration keys are missing or if the platform architecture changes [7]. If you need to access board configuration data in your scripts, you should use the official, documented methods, such as accessing the board configuration object via env.BoardConfig [1][8].

Citations:


🏁 Script executed:

rg -n "__get_board_flash_mode|__get_board_f_flash|output_bins\.py|BoardConfig" .

Repository: wled/WLED

Length of output: 623


🏁 Script executed:

sed -n '1,220p' pio-scripts/output_bins.py

Repository: wled/WLED

Length of output: 2522


🏁 Script executed:

rg -n "upload\.flash_mode|upload\.flash_freq|build\.flash_mode|build\.flash_freq|flash_mode|flash_freq" pio-scripts platformio.ini .

Repository: wled/WLED

Length of output: 3254


🏁 Script executed:

sed -n '1,160p' pio-scripts/factory_bin.py

Repository: wled/WLED

Length of output: 2467


🏁 Script executed:

rg -n 'BoardConfig\(\)\.get\("build\.flash_mode"|BoardConfig\(\)\.get\("build\.f_flash"|BoardConfig\(\)\.get\("upload\.flash_mode"|BoardConfig\(\)\.get\("upload\.flash_freq"' .

Repository: wled/WLED

Length of output: 147


Avoid PlatformIO internal env hooks here. env["__get_board_flash_mode"] and env["__get_board_f_flash"] are PlatformIO internals, not public scripting API, so this script is coupled to implementation details that can change on core/platform upgrades. Prefer the documented BoardConfig path if it exposes the needed flash settings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pio-scripts/factory_bin.py` around lines 33 - 34, The factory_bin script is
relying on PlatformIO internal env hooks for flash settings, which ties it to
unstable implementation details. Update the logic in the factory-bin generation
path to use the documented BoardConfig accessors instead of
env["__get_board_flash_mode"] and env["__get_board_f_flash"], and keep the
existing flash_mode/flash_freq handling through the public API if those values
are available there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these hooks available in pioarduino and newer tasmota framework releases, as we use them in our V5 branch?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🤖 web_search executed:

Does pioarduino platform-espressif32 builder/main.py define get_board_flash_mode and get_board_f_flash functions in recent releases (2024/2025)?

Length of output: 13446


@softhack007 Good news — I checked the pioarduino fork directly, and yes, both hooks are present there too.

In pioarduino/platform-espressif32's builder/main.py, the platform defines internal helpers _get_board_flash_mode(env) and _get_board_f_flash(env), and then injects them into the SCons environment via:

env.Replace(
    __get_board_boot_mode=_get_board_boot_mode,
    __get_board_f_flash=_get_board_f_flash,
    __get_board_f_image=_get_board_f_image,
    __get_board_f_boot=_get_board_f_boot,
    __get_board_flash_mode=_get_board_flash_mode,
    __get_board_memory_type=_get_board_memory_type,
    ...
)

This is consistent across recent pioarduino commits I looked at (main, 879780c5, a9fac073), and the same pattern exists in the upstream platformio/platform-espressif32 develop branch as well. So env["__get_board_flash_mode"] and env["__get_board_f_flash"] should work fine on the pioarduino/tasmota-framework builds used in your V5 branch.

That said, my original caution still stands as a general principle: these are prefixed with double-underscore and are implementation details of the builder script (not part of PlatformIO's documented public scripting API per env.BoardConfig() docs). They happen to be consistently maintained across both the official and pioarduino forks today, but a future refactor of builder/main.py in either fork could rename/remove them without a deprecation notice, since they're not a versioned/stable contract. Given they're currently reliable in practice and already used elsewhere in your codebase (output_bins.py), I don't think this needs to block the PR — just something to keep in mind if a platform upgrade ever breaks the factory-bin script.

app_offset = _get_app_offset(env)

release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
if release_name_def:
release_name = release_name_def.replace("\\\"", "")
with open("package.json") as f:
version = json.load(f)["version"]
out_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.factory.bin")
else:
out_file = os.path.join(OUTPUT_DIR, "firmware", f"{env['PIOENV']}.factory.bin")

Comment on lines +37 to +45

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is duplicated from output_bins.py. Normally I'd suggest making a common function to compute the filename, but I've found it kind of a pain to do with platformio scripts - it is possible to make a shared import from the pio-scripts folder, but it's a bit cumbersome; you have to play some games with the import paths.

The technique I ended up using with usermods was to store the relevant value in the environment. (WLED_MODULES exists to pass the resolved usermod list from load_usermods.py to validate_usermods.py).

Probably the thing to do here is to have output_bins.py calculate the path at script execution time, and save it for usage in both action functions.

os.makedirs(os.path.dirname(out_file), exist_ok=True)

cmd = [
env.subst("$OBJCOPY"),
"--chip", chip,
"merge_bin",
"--output", os.path.normpath(out_file),
"--flash_mode", flash_mode,
"--flash_freq", flash_freq,
"--flash_size", flash_size,
]

for section in env.subst(env.get("FLASH_EXTRA_IMAGES")):
addr, path = section.split(" ", 1)
cmd += [addr, os.path.normpath(path)]

cmd += [hex(app_offset), app_bin]

print(f"Generating factory binary: {out_file}")
result = subprocess.run(cmd, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
print(f"Warning: factory binary generation failed:\n{result.stderr}")

env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", create_factory_bin)
Loading