Skip to content

Commit 815995a

Browse files
author
Marek Fiala
committed
blog: Added idf.py extensions article
1 parent 14b33c5 commit 815995a

File tree

4 files changed

+295
-2
lines changed

4 files changed

+295
-2
lines changed
466 KB
Loading
32.9 KB
Loading
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
---
2+
title: "Extending idf.py: Create custom commands for your ESP-IDF workflow"
3+
date: "2025-10-10"
4+
showAuthor: false
5+
authors:
6+
- "marek-fiala"
7+
tags: ["ESP-IDF", "idf.py", "CLI", "Extensions", "Development Tools"]
8+
summary: "Learn how to extend idf.py with custom commands for your development workflow. This guide covers both component-based extensions for project-specific tools and Python package extensions for reusable commands, with practical examples and best practices for seamless integration."
9+
---
10+
11+
<!-- TODO[Remove after ESP-IDF v6.0 release]
12+
Note: This article mentions ESP-IDF v6.0. Once it is released, remove the note below about development status.
13+
Context: Developer Portal's GitLab MR `88#note_2327272`
14+
ReviewDate: 2026-01-15
15+
Tags: ESP-IDF v6.0, obsolete
16+
-->
17+
18+
What if you could extend `idf.py` with your own custom commands tailored to your specific workflow? With the ESP-IDF v6.0 and newer (to be released soon), you can do exactly that through a powerful extension system that lets you add project-specific tools or distribute reusable commands across your projects.
19+
20+
Before we dive into extensions, let’s recall what `idf.py` gives you out of the box. It’s the central command-line tool for ESP-IDF that allows you to:
21+
22+
- Set the target chip using `idf.py set-target` (like esp32)
23+
- Tweak your project settings using `idf.py menuconfig`
24+
- Build your application using `idf.py build`
25+
- Flash it using `idf.py -p PORT flash`
26+
- Watch the logs in real time using `idf.py monitor`
27+
28+
For most developers, the daily cycle is simply **build → flash → monitor** — all streamlined under one command.
29+
30+
## Why extend idf.py?
31+
32+
Sometimes, though, the built-in commands aren’t enough. Maybe you need a custom deployment command that packages your firmware with metadata, or perhaps you want to integrate with your CI/CD pipeline through specialized build targets. Rather than maintaining separate scripts, you can now integrate these directly into `idf.py`, giving you:
33+
34+
- **Unified interface**: All your tools accessible through the familiar `idf.py` command
35+
- **Consistent help system**: Your commands appear in `idf.py --help` with proper documentation
36+
- **Shared options**: Leverage existing global options like `--port` and `--build-dir`
37+
- **Dependency management**: Ensure commands run in the right order automatically
38+
39+
## Two ways to extend idf.py
40+
41+
ESP-IDF supports two extension mechanisms, each suited for different use cases:
42+
43+
- Component-based extensions
44+
- Python package extensions
45+
46+
### Component-based extensions
47+
48+
This is the case for project-specific commands that should only be available when working with a particular project or component.
49+
50+
**How it works**: Place a file named `idf_ext.py` in your component directory. ESP-IDF automatically discovers and loads extensions from this file **after the project is configured** with `idf.py reconfigure` or `idf.py build`. Within the component-based extension, the name of the file is important, as `idf.py` searches exactly for `idf_ext.py`.
51+
52+
**Note**: You may also place `idf_ext.py` in the project root instead of a component. This option has existed in earlier ESP-IDF versions and works the same way, but using a dedicated component is recommended for clarity and reusability.
53+
54+
#### Step 1: Create the extension file
55+
56+
- Create a new component (or use an existing one).
57+
- Inside the component, add a file named `idf_ext.py`.
58+
- This file must implement an `action_extensions` function returning a dictionary that describes your new commands, options, and callbacks.
59+
60+
**Example (sensor manager)**:
61+
In this example, we’ll add a new command sensor-info that prints configuration details about sensors in your project. Start by creating a component called `sensor_manager`:
62+
63+
```bash
64+
# Create the component using idf.py
65+
idf.py create-component -C components sensor_manager
66+
```
67+
68+
Then, inside your component directory `components/sensor_manager/`, create `idf_ext.py` Python file and place the following code:
69+
70+
```python
71+
from typing import Any
72+
import click
73+
74+
def action_extensions(base_actions: dict, project_path: str) -> dict:
75+
def sensor_info(subcommand_name: str, ctx: click.Context, global_args: dict, **action_args: Any) -> None:
76+
sensor_type = action_args.get('type', 'all')
77+
verbose = getattr(global_args, 'detail', False)
78+
79+
print(f"Running {subcommand_name} for sensor type: {sensor_type}")
80+
if verbose:
81+
print(f"Project path: {project_path}")
82+
print("Detailed sensor configuration would be displayed here...")
83+
84+
def global_callback_detail(ctx: click.Context, global_args: dict, tasks: list) -> None:
85+
if getattr(global_args, 'detail', False):
86+
print(f"About to execute {len(tasks)} task(s): {[t.name for t in tasks]}")
87+
88+
return {
89+
"version": "1",
90+
"global_options": [
91+
{
92+
"names": ["--detail", "-d"],
93+
"is_flag": True,
94+
"help": "Enable detailed output for all commands",
95+
}
96+
],
97+
"global_action_callbacks": [global_callback_detail],
98+
"actions": {
99+
"sensor-info": {
100+
"callback": sensor_info,
101+
"short_help": "Display sensor configuration",
102+
"help": "Show detailed information about sensor configuration and status",
103+
"options": [
104+
{
105+
"names": ["--type", "-t"],
106+
"help": "Sensor type to query (temperature, humidity, pressure, or all)",
107+
"default": "all",
108+
"type": click.Choice(['temperature', 'humidity', 'pressure', 'all']),
109+
}
110+
]
111+
},
112+
},
113+
}
114+
```
115+
116+
#### Step 2: Register the component
117+
118+
- Ensure the new component is registered in your project’s CMakeLists.txt.
119+
- Further information on how to register commponents can be found in [Espressif documentation](https://docs.espressif.com/projects/esp-idf/en/stable/api-guides/build-system.html#component-requirements).
120+
121+
<!-- Now you need to make sure your component is registered in your project's main `CMakeLists.txt`: -->
122+
123+
**Example (sensor manager)**:
124+
Update your project's main `CMakeLists.txt`
125+
```cmake
126+
idf_component_register(
127+
SRCS "main.c"
128+
INCLUDE_DIRS "."
129+
REQUIRES "sensor_manager" # This makes the extension available
130+
)
131+
```
132+
133+
#### Step 3: Load and test
134+
- Reconfigure or build the project to let ESP-IDF discover the extension.
135+
- Run idf.py help to check that your new command appears.
136+
- Test the new command with its options.
137+
138+
**Example (sensor manager)**: In our case, the extension adds the `sensor-info` command:
139+
140+
```bash
141+
# Configure the project to discover the extension
142+
idf.py reconfigure
143+
144+
# Check that your command appears in help
145+
idf.py --help
146+
147+
# Try your new command
148+
idf.py sensor-info --type temperature
149+
idf.py --detail sensor-info --type all
150+
```
151+
152+
### Python package extensions
153+
154+
This is ideal for reusable tools that you want to share across multiple projects or distribute to your team.
155+
156+
**How it works**: Create a Python package with an entry point in the `idf_extension` group. Once installed, the extension is available globally for all projects.
157+
158+
#### Step 1: Create the package structure
159+
160+
- Create a new folder for your tool.
161+
- Add a `pyproject.toml` file to describe the package.
162+
- Inside the folder, create a subfolder with the same name, which will contain your Python code.
163+
- Inside that subfolder, add `__init__.py` and a Python file for the extension (e.g., `esp_ext.py`).
164+
- Unlike the fixed `idf_ext.py` in component-based extensions, the filename here is flexible because it is explicitly referenced in `pyproject.toml`.
165+
- For clarity and consistency, it’s recommended to prefix it with your tool name and suffix it with `_ext.py`.
166+
167+
The resulting structure should look like this:
168+
169+
```bash
170+
my_sensor_tools/
171+
├── pyproject.toml # describe the package here
172+
├── my_sensor_tools/ # place your Python code here
173+
│ ├── __init__.py
174+
│ └── esp_ext.py
175+
```
176+
177+
#### Step 2: Fill the extension file
178+
179+
- Implement the `action_extensions` function inside your package’s Python file.
180+
181+
**Example (sensor manager)**:
182+
183+
Here we simply copy the `action_extensions` function from the component example into `my_sensor_tools/esp_ext.py`.
184+
185+
#### Step 3: Configure and install
186+
187+
- Define the Python entry-point in your `pyproject.toml` under `[project.entry-points.idf_extension]`.
188+
- Use the format name: `package.module:function`.
189+
- Install the package (for development, use `pip install -e .`).
190+
- The new command will now be globally available in any ESP-IDF project.
191+
192+
<!-- On the final step, you will need to create the `pyproject.toml` file: -->
193+
**Example (sensor manager)**:
194+
195+
The `pyproject.toml` file for our example could look like this:
196+
197+
```toml
198+
[project]
199+
name = "my-sensor-tools"
200+
version = "1.0.0"
201+
202+
# Register the extension under the `idf_extension` group,
203+
# so ESP-IDF can automatically discover it
204+
[project.entry-points.idf_extension]
205+
my_sensor_tools = "my_sensor_tools.esp_ext:action_extensions"
206+
```
207+
208+
Install and use:
209+
210+
```bash
211+
# Install in development mode
212+
cd my_sensor_tools
213+
pip install -e .
214+
215+
# Your command is now available in any ESP-IDF project
216+
cd my_sensor_tools/
217+
idf.py sensor-info --type temperature
218+
idf.py --detail sensor-info --type all
219+
```
220+
221+
## Naming conventions
222+
223+
- **Avoid conflicts**: Your commands cannot override built-in `idf.py` commands like `build`, `flash`, or `monitor`
224+
- **Use descriptive names**: Prefer `sensor-info` over `info` to avoid ambiguity
225+
- **Package prefixes**: For Python package extensions, consider prefixing commands with your tool name
226+
227+
## Advanced features
228+
229+
Do you need something extra? Beyond simple commands, the extension system also gives you ways to define global options, control execution order, and build richer command-line interfaces. These features let you create tools that feel fully integrated with the rest of `idf.py`.
230+
231+
### Global options and callbacks
232+
233+
The extension system supports sophisticated features for power users:
234+
235+
**Global options**: Define options that work across all commands. Can be exposed under `global_args` parameter.
236+
237+
**Global callbacks**: Functions that run before any tasks execute, perfect for validation, logging, or injecting additional tasks based on global options.
238+
239+
### Dependencies and order management
240+
241+
Ensure your commands run in the correct sequence:
242+
243+
```python
244+
"actions": {
245+
"deploy": {
246+
"callback": deploy_firmware,
247+
"dependencies": ["all"], # Always build before deploying
248+
"order_dependencies": ["flash"], # If flash is requested, run it before deploy
249+
"help": "Deploy firmware to production servers"
250+
}
251+
}
252+
```
253+
254+
### Rich argument support
255+
256+
Support complex command-line interfaces:
257+
258+
```python
259+
"options": [
260+
{
261+
"names": ["--config-file", "-c"],
262+
"type": click.Path(exists=True),
263+
"help": "Configuration file path"
264+
},
265+
{
266+
"names": ["--verbose", "-v"],
267+
"count": True, # -v, -vv, -vvv for different verbosity levels
268+
"help": "Increase verbosity (use multiple times)"
269+
}
270+
],
271+
"arguments": [
272+
{
273+
"names": ["targets"],
274+
"nargs": -1, # Accept multiple targets
275+
"required": True
276+
}
277+
]
278+
```
279+
280+
For more details on the extension API and additional features, see the [Click documentation](https://click.palletsprojects.com/) for argument types and the [ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tools/idf-py.html#extending-idf-py) for the complete extension reference.
281+
282+
## Conclusion
283+
284+
The `idf.py` extension system opens up powerful possibilities for customizing your ESP-IDF development workflow. Whether you're adding simple project-specific helpers or building sophisticated development tools, extensions let you integrate seamlessly with the existing ESP-IDF ecosystem.
285+
286+
Start small with a component-based extension for your current project, then graduate to distributable packages as your tools mature.
287+
288+
## What's next?
289+
290+
- Explore the [full extension API documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tools/idf-py.html#extending-idf-py) for advanced features
291+
- Check out existing extensions in the ESP-IDF codebase for inspiration
292+
293+
Happy extending!

data/authors/marek-fiala.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"name": "Marek Fiala",
3-
"bio": "",
4-
"image": ""
3+
"bio": "SW Developer at Espressif",
4+
"image": "img/authors/marek-fiala.webp"
55
}

0 commit comments

Comments
 (0)