Skip to content

Commit aa80001

Browse files
committed
feat(espefuse): Refactor CLI and use click for parsing arguments
1 parent 9104038 commit aa80001

27 files changed

+4597
-5729
lines changed

espefuse/__init__.py

Lines changed: 146 additions & 269 deletions
Large diffs are not rendered by default.

espefuse/cli_util.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
#
3+
# SPDX-License-Identifier: GPL-2.0-or-later
4+
5+
from collections import namedtuple
6+
from io import StringIO
7+
8+
import rich_click as click
9+
from espefuse.efuse.base_operations import BaseCommands
10+
import esptool
11+
from esptool.cli_util import Group as EsptoolGroup
12+
from esptool.logger import log
13+
14+
import espefuse.efuse.esp32 as esp32_efuse
15+
import espefuse.efuse.esp32c2 as esp32c2_efuse
16+
import espefuse.efuse.esp32c3 as esp32c3_efuse
17+
import espefuse.efuse.esp32c5 as esp32c5_efuse
18+
import espefuse.efuse.esp32c6 as esp32c6_efuse
19+
import espefuse.efuse.esp32c61 as esp32c61_efuse
20+
import espefuse.efuse.esp32h2 as esp32h2_efuse
21+
import espefuse.efuse.esp32h21 as esp32h21_efuse
22+
import espefuse.efuse.esp32h4 as esp32h4_efuse
23+
import espefuse.efuse.esp32p4 as esp32p4_efuse
24+
import espefuse.efuse.esp32s2 as esp32s2_efuse
25+
import espefuse.efuse.esp32s3 as esp32s3_efuse
26+
27+
28+
DefChip = namedtuple("DefChip", ["chip_name", "efuse_lib", "chip_class"])
29+
30+
SUPPORTED_BURN_COMMANDS = [
31+
"read-protect-efuse",
32+
"write-protect-efuse",
33+
"burn-efuse",
34+
"burn-block-data",
35+
"burn-bit",
36+
"burn-key",
37+
"burn-key-digest",
38+
"burn-custom-mac",
39+
"set-flash-voltage",
40+
"execute-scripts",
41+
]
42+
43+
SUPPORTED_READ_COMMANDS = [
44+
"summary",
45+
"dump",
46+
"get-custom-mac",
47+
"adc-info",
48+
"check-error",
49+
]
50+
51+
SUPPORTED_COMMANDS = SUPPORTED_READ_COMMANDS + SUPPORTED_BURN_COMMANDS
52+
53+
SUPPORTED_CHIPS = {
54+
"esp32": DefChip("ESP32", esp32_efuse, esptool.targets.ESP32ROM),
55+
"esp32c2": DefChip("ESP32-C2", esp32c2_efuse, esptool.targets.ESP32C2ROM),
56+
"esp32c3": DefChip("ESP32-C3", esp32c3_efuse, esptool.targets.ESP32C3ROM),
57+
"esp32c6": DefChip("ESP32-C6", esp32c6_efuse, esptool.targets.ESP32C6ROM),
58+
"esp32c61": DefChip("ESP32-C61", esp32c61_efuse, esptool.targets.ESP32C61ROM),
59+
"esp32c5": DefChip("ESP32-C5", esp32c5_efuse, esptool.targets.ESP32C5ROM),
60+
"esp32h2": DefChip("ESP32-H2", esp32h2_efuse, esptool.targets.ESP32H2ROM),
61+
"esp32h21": DefChip("ESP32-H21", esp32h21_efuse, esptool.targets.ESP32H21ROM),
62+
"esp32h4": DefChip("ESP32-H4", esp32h4_efuse, esptool.targets.ESP32H4ROM),
63+
"esp32p4": DefChip("ESP32-P4", esp32p4_efuse, esptool.targets.ESP32P4ROM),
64+
"esp32s2": DefChip("ESP32-S2", esp32s2_efuse, esptool.targets.ESP32S2ROM),
65+
"esp32s3": DefChip("ESP32-S3", esp32s3_efuse, esptool.targets.ESP32S3ROM),
66+
}
67+
68+
69+
def get_command_class(chip_name: str) -> BaseCommands:
70+
return SUPPORTED_CHIPS[chip_name].efuse_lib.commands() # type: ignore
71+
72+
73+
click.rich_click.USE_CLICK_SHORT_HELP = True
74+
click.rich_click.COMMAND_GROUPS = {
75+
"espefuse.py": [
76+
{
77+
"name": "Burn commands",
78+
"commands": SUPPORTED_BURN_COMMANDS,
79+
},
80+
{
81+
"name": "Read commands",
82+
"commands": SUPPORTED_READ_COMMANDS,
83+
},
84+
]
85+
}
86+
87+
88+
class Group(EsptoolGroup):
89+
DEPRECATED_OPTIONS = {
90+
"--file_name": "--file-name",
91+
}
92+
93+
@staticmethod
94+
def _split_to_groups(args: list[str]) -> tuple[list[list[str]], list[str]]:
95+
"""
96+
This function splits the args list into groups,
97+
where each item is a cmd with all its args.
98+
99+
Example:
100+
all_args:
101+
['burn-key-digest', 'secure_images/ecdsa256_secure_boot_signing_key_v2.pem',
102+
'burn-key', 'BLOCK_KEY0', 'images/efuse/128bit_key',
103+
'XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS']
104+
105+
used_cmds: ['burn-key-digest', 'burn-key']
106+
groups:
107+
[['burn-key-digest', 'secure_images/ecdsa256_secure_boot_signing_key_v2.pem'],
108+
['burn-key', 'BLOCK_KEY0', 'images/efuse/128bit_key',
109+
'XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS']]
110+
"""
111+
groups: list[list[str]] = []
112+
args_group: list[str] = []
113+
used_cmds: list[str] = []
114+
for arg in args:
115+
if arg.replace("_", "-") in SUPPORTED_COMMANDS:
116+
groups.append(args_group)
117+
used_cmds.append(arg)
118+
args_group = [arg]
119+
else:
120+
args_group.append(arg)
121+
groups.append(args_group)
122+
return groups, used_cmds
123+
124+
def parse_args(self, ctx: click.Context, args: list[str]):
125+
ctx.ensure_object(dict)
126+
ctx.obj["is_help"] = any(help_arg in args for help_arg in ctx.help_option_names)
127+
idx = (
128+
args.index("--chip")
129+
if "--chip" in args
130+
else (args.index("-c") if "-c" in args else -1)
131+
)
132+
ctx.obj["chip"] = args[idx + 1] if idx != -1 and idx + 1 < len(args) else "auto"
133+
# override the default behavior of EsptoolGroup, because we don't need
134+
# support for parameters with nargs=-1
135+
args = self._replace_deprecated_args(args)
136+
_, used_cmds = self._split_to_groups(args)
137+
138+
if len(used_cmds) == 0:
139+
self.get_help(ctx)
140+
ctx.exit()
141+
142+
ctx.obj["used_cmds"] = used_cmds
143+
ctx.obj["args"] = args
144+
return super(click.RichGroup, self).parse_args(ctx, args)
145+
146+
def get_help(self, ctx: click.Context) -> str:
147+
# help was called without any commands, so we need to add the commands for the
148+
# default chip
149+
if not self.list_commands(ctx):
150+
chip = ctx.obj["chip"]
151+
if chip == "auto":
152+
log.note(
153+
"Chip not specified, showing commands for ESP32 by default. "
154+
"Specify the --chip option to get chip-specific help."
155+
)
156+
chip = "esp32"
157+
# TODO: this is a hack to get the full list of commands, we need to find
158+
# a better way to do this
159+
commands = get_command_class(chip)
160+
esp = SUPPORTED_CHIPS[chip].chip_class(port=StringIO(), baud=115200)
161+
commands.efuses = SUPPORTED_CHIPS[chip].efuse_lib.EspEfuses(esp, True) # type: ignore
162+
commands.add_cli_commands(self)
163+
return super().get_help(ctx) # type: ignore

0 commit comments

Comments
 (0)