ChaosDMX
Open-Source DMX-Interface
Loading...
Searching...
No Matches
tasks.py
Go to the documentation of this file.
1# cspell:ignore: JTAG UART FTDI Espressif
2
3from invoke import task
4from invoke.exceptions import Exit
5import os
6import serial.tools.list_ports
7import shutil
8import webbrowser
9
10
11TARGET_BOARDS = {
12 "lolinS2mini": "sdkconfig.defaults",
13 # "lolinS3mini": "sdkconfig.defaults.lolinS3mini", <-- can be added automatically for releases
14}
15
16
18 """Finds the first matching USB port based on known ESP VIDs/PIDs."""
19 # Known IDs for ESP boards and common USB-UART bridges
20 ESP_IDENTIFIERS = [
21 (0x303A, None), # Espressif (e.g., ESP32-S3/C3 native USB-JTAG-Serial)
22 (0x10C4, 0xEA60), # Silicon Labs CP210x (very common on ESP32)
23 (0x1A86, 0x7523), # WCH CH340 (common on budget ESP boards)
24 (0x0403, 0x6001), # FTDI FT232R
25 ]
26
27 ports = serial.tools.list_ports.comports()
28
29 for port in ports:
30 # Check if VID/PID match
31 for vid, pid in ESP_IDENTIFIERS:
32 if port.vid == vid and (pid is None or port.pid == pid):
33 return port.device
34
35 # Fallback to default if no matching device is found
36 return "/dev/ttyUSB0"
37
38
39@task
40def build(c, board="lolinS2mini"):
41 """Build the project for a specific board (default: lolinS2mini)"""
42 if board not in TARGET_BOARDS:
43 print(f"āŒ Error: Board '{board}' is not defined in TARGET_BOARDS.")
44 print(f"Available boards: {', '.join(TARGET_BOARDS.keys())}")
45 raise Exit(code=1)
46
47 defaults_file = TARGET_BOARDS[board]
48
49 print(f"-> Building for board: {board} using {defaults_file}")
50 c.run(f"idf.py -D SDKCONFIG_DEFAULTS={defaults_file} build", pty=True)
51
52
53@task
54def flash(c, port=None):
55 """Flash the project to device"""
56 target_port = port if port else _find_esp_port()
57 print(f"-> Using serial port for flashing: {target_port}")
58 c.run(f"idf.py -p {target_port} flash", pty=True)
59
60
61@task
62def monitor(c, port=None):
63 """Monitor serial output from device"""
64 target_port = port if port else _find_esp_port()
65 print(f"-> Using serial port: {target_port}")
66 c.run(f"idf.py monitor -p {target_port}", pty=True)
67
68
69@task
70def release(c):
71 """Build single binaries for release across all targets"""
72 version = os.environ.get("GITHUB_REF_NAME", "dev")
73
74 dist_dir = "dist"
75 if os.path.exists(dist_dir):
76 shutil.rmtree(dist_dir)
77 os.makedirs(dist_dir)
78
79 for board, defaults_file in TARGET_BOARDS.items():
80 print("\n=========================================")
81 if not os.path.exists(defaults_file):
82 print(f"āš ļø Warning: {defaults_file} not found. Skipping {board}...")
83 continue
84
85 print(f"šŸš€ Building Target: {board} ({defaults_file})")
86 print("=========================================")
87
88 c.run("idf.py fullclean", pty=True)
89 if os.path.exists("sdkconfig"):
90 os.remove("sdkconfig")
91
92 build_cmd = f"idf.py -D SDKCONFIG_DEFAULTS={defaults_file} -D CMAKE_BUILD_TYPE=Release build"
93 c.run(build_cmd, pty=True)
94
95 c.run("idf.py merge-bin", pty=True)
96
97 source_bin = "build/merged-binary.bin"
98 target_bin = os.path.join(dist_dir, f"ChaosDMX-{board}-{version}.bin")
99
100 if os.path.exists(source_bin):
101 shutil.copy(source_bin, target_bin)
102 print(f"āž” Asset successfully created: {target_bin}")
103 else:
104 print(f"āŒ Error: {source_bin} was not created.")
105 raise Exit(code=1)
106
107
108@task
109def clean(c):
110 """Clean build artifacts"""
111 c.run("idf.py fullclean", pty=True)
112
113
114@task
115def config(c):
116 """Open menuconfig to edit project settings"""
117 c.run("idf.py menuconfig", pty=True)
118
119
120@task
122 """Save current config as sdkconfig.defaults"""
123 c.run("idf.py save-defconfig", pty=True)
124
125
126@task
127def update(c):
128 """Update project dependencies"""
129 c.run("idf.py update-dependencies", pty=True)
130 c.run("nix flake update", pty=True)
131
132
133@task
134def reset(c):
135 """Reset project to clean state: remove build, config, and managed components"""
136 files_to_remove = [
137 ".cspellcache",
138 ".pre-commit-config.yaml",
139 "bootloader.bin",
140 "sdkconfig",
141 "sdkconfig.old",
142 ]
143 dirs_to_remove = [
144 ".ruff_cache",
145 "build",
146 "docs/doxygen",
147 "managed_components",
148 ]
149
150 for f in files_to_remove:
151 if os.path.exists(f):
152 os.remove(f)
153
154 for d in dirs_to_remove:
155 if os.path.exists(d):
156 shutil.rmtree(d)
157
158
159@task
160def format(c):
161 """Format all source files using pre-commit hooks"""
162 c.run("pre-commit run --all-files", pty=True)
163
164
165@task(help={"o": "Open documentation in the default browser after generation."})
166def docs(c, o=False):
167 """Generate Doxygen documentation."""
168 result = c.run("doxygen Doxyfile", warn=True)
169 if result.ok:
170 path = "docs/doxygen/html/index.html"
171 print(f"\nāœ“ Documentation generated in {path}")
172 if o:
173 webbrowser.open(f"file://{os.path.abspath(path)}")
174 return
175 raise Exit(code=1)
176
177
178@task
180 """List doxygen coverage of documentation."""
181 c.run("python tools/doxy-coverage.py docs/doxygen/xml --no-error", pty=True)
build(c, board="lolinS2mini")
Definition tasks.py:40
release(c)
Definition tasks.py:70
docs(c, o=False)
Definition tasks.py:166
format(c)
Definition tasks.py:160
_find_esp_port()
Definition tasks.py:17
monitor(c, port=None)
Definition tasks.py:62
config(c)
Definition tasks.py:115
docs_coverage(c)
Definition tasks.py:179
clean(c)
Definition tasks.py:109
reset(c)
Definition tasks.py:134
update(c)
Definition tasks.py:127
flash(c, port=None)
Definition tasks.py:54
saveconfig(c)
Definition tasks.py:121