@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +22 -35
  2. package/dist/as-built.d.ts +33 -0
  3. package/dist/as-built.js +188 -0
  4. package/dist/async/timer-polyfill.js +1 -1
  5. package/dist/boardgen.d.ts +128 -0
  6. package/dist/boardgen.js +1366 -0
  7. package/dist/chips/board-overrides.d.ts +7 -0
  8. package/dist/chips/board-overrides.js +11 -0
  9. package/dist/chips/controllers.d.ts +28 -8
  10. package/dist/chips/controllers.js +49 -12
  11. package/dist/chips/esp32s3.js +49 -0
  12. package/dist/chips/index.d.ts +6 -12
  13. package/dist/chips/index.js +18 -34
  14. package/dist/chips/resolve.d.ts +16 -2
  15. package/dist/chips/resolve.js +298 -15
  16. package/dist/chips/soc/.d.ts +2 -0
  17. package/dist/chips/soc/.js +129 -0
  18. package/dist/chips/soc/esp32.d.ts +2 -0
  19. package/dist/chips/soc/esp32.js +120 -0
  20. package/dist/chips/soc/esp32c3.d.ts +2 -0
  21. package/dist/chips/soc/esp32c3.js +90 -0
  22. package/dist/chips/soc/esp32c6.d.ts +2 -0
  23. package/dist/chips/soc/esp32c6.js +109 -0
  24. package/dist/chips/soc/esp32s3.d.ts +2 -0
  25. package/dist/chips/soc/esp32s3.js +189 -0
  26. package/dist/chips/soc/index.d.ts +2 -0
  27. package/dist/chips/soc/index.js +23 -0
  28. package/dist/chips/soc/nrf52840.d.ts +2 -0
  29. package/dist/chips/soc/nrf52840.js +130 -0
  30. package/dist/chips/soc/rp2040.d.ts +2 -0
  31. package/dist/chips/soc/rp2040.js +141 -0
  32. package/dist/chips/soc/rp2350a.d.ts +2 -0
  33. package/dist/chips/soc/rp2350a.js +145 -0
  34. package/dist/chips/soc/samd21g18a.d.ts +2 -0
  35. package/dist/chips/soc/samd21g18a.js +143 -0
  36. package/dist/chips/soc/stm32f411xe.d.ts +2 -0
  37. package/dist/chips/soc/stm32f411xe.js +251 -0
  38. package/dist/chips/types.d.ts +382 -12
  39. package/dist/chips/xiao-ble.js +33 -0
  40. package/dist/display/profiles.js +1 -1
  41. package/dist/doctor.js +38 -4
  42. package/dist/dt-config/custom-board.d.ts +23 -0
  43. package/dist/dt-config/custom-board.js +227 -0
  44. package/dist/dt-config/kconfig.d.ts +83 -1
  45. package/dist/dt-config/kconfig.js +116 -27
  46. package/dist/dt-config/overlay.js +623 -18
  47. package/dist/framework.manifest.d.ts +20 -30
  48. package/dist/framework.manifest.js +124 -178
  49. package/dist/index.d.ts +4 -1
  50. package/dist/index.js +8 -2
  51. package/dist/lowering/adc.d.ts +26 -5
  52. package/dist/lowering/adc.js +137 -31
  53. package/dist/lowering/ble.js +31 -28
  54. package/dist/lowering/dac.js +9 -9
  55. package/dist/lowering/fs.js +18 -10
  56. package/dist/lowering/gpio.d.ts +2 -0
  57. package/dist/lowering/gpio.js +90 -10
  58. package/dist/lowering/http.js +39 -14
  59. package/dist/lowering/hwtimer.d.ts +4 -0
  60. package/dist/lowering/hwtimer.js +35 -10
  61. package/dist/lowering/i2c.js +46 -54
  62. package/dist/lowering/index.d.ts +5 -5
  63. package/dist/lowering/index.js +15 -16
  64. package/dist/lowering/interrupts.d.ts +22 -5
  65. package/dist/lowering/interrupts.js +104 -22
  66. package/dist/lowering/mqtt.js +12 -6
  67. package/dist/lowering/power.js +0 -4
  68. package/dist/lowering/preferences.js +49 -77
  69. package/dist/lowering/pulse.js +5 -36
  70. package/dist/lowering/pwm.d.ts +32 -3
  71. package/dist/lowering/pwm.js +105 -17
  72. package/dist/lowering/random.js +3 -3
  73. package/dist/lowering/sensor.d.ts +37 -0
  74. package/dist/lowering/sensor.js +119 -0
  75. package/dist/lowering/spi.d.ts +12 -0
  76. package/dist/lowering/spi.js +65 -40
  77. package/dist/lowering/thread.d.ts +14 -0
  78. package/dist/lowering/thread.js +55 -0
  79. package/dist/lowering/timing.js +20 -46
  80. package/dist/lowering/tone.js +18 -5
  81. package/dist/lowering/uart.d.ts +13 -0
  82. package/dist/lowering/uart.js +72 -51
  83. package/dist/lowering/usb.d.ts +34 -0
  84. package/dist/lowering/usb.js +206 -0
  85. package/dist/lowering/wdt.d.ts +1 -1
  86. package/dist/lowering/wdt.js +17 -7
  87. package/dist/lowering/wifi.d.ts +3 -2
  88. package/dist/lowering/wifi.js +120 -111
  89. package/dist/sdk/board-catalog-sync.d.ts +16 -0
  90. package/dist/sdk/board-catalog-sync.js +41 -0
  91. package/dist/sdk/board-data.generated.d.ts +2 -0
  92. package/dist/sdk/board-data.generated.js +4 -0
  93. package/dist/sdk/catalog-walker.d.ts +90 -0
  94. package/dist/sdk/catalog-walker.js +682 -0
  95. package/dist/sdk/dts-reader.d.ts +83 -0
  96. package/dist/sdk/dts-reader.js +596 -0
  97. package/dist/strategy.d.ts +136 -17
  98. package/dist/strategy.js +881 -212
  99. package/dist/toolchain/bossac-touch.d.ts +18 -0
  100. package/dist/toolchain/bossac-touch.js +88 -0
  101. package/dist/toolchain/compat.d.ts +2 -2
  102. package/dist/toolchain/compat.js +8 -3
  103. package/dist/toolchain/debug-config.d.ts +21 -26
  104. package/dist/toolchain/debug-config.js +212 -72
  105. package/dist/toolchain/env-check.d.ts +4 -4
  106. package/dist/toolchain/env-check.js +5 -5
  107. package/dist/toolchain/index.d.ts +66 -1
  108. package/dist/toolchain/index.js +713 -58
  109. package/dist/toolchain/scaffold.d.ts +9 -0
  110. package/dist/toolchain/scaffold.js +84 -7
  111. package/dist/toolchain/west-discover.d.ts +8 -1
  112. package/dist/toolchain/west-discover.js +19 -6
  113. package/dist/toolchain/west-spawn.js +17 -5
  114. package/installer/CHANGELOG.md +473 -0
  115. package/installer/README.md +328 -0
  116. package/installer/environment.yml +46 -0
  117. package/installer/etc/conda/activate.d/zephyr.bat +7 -0
  118. package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
  119. package/installer/etc/conda/activate.d/zephyr.sh +20 -0
  120. package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
  121. package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
  122. package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
  123. package/installer/install.mjs +580 -0
  124. package/installer/install.ps1 +619 -0
  125. package/installer/install.sh +310 -0
  126. package/installer/lib/detect-platform.sh +62 -0
  127. package/installer/lib/fetch-sdk.sh +304 -0
  128. package/installer/lib/init-workspace.sh +153 -0
  129. package/installer/lib/write-activation.sh +76 -0
  130. package/installer/patches/tfm-c-include-separators.patch +19 -0
  131. package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
  132. package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
  133. package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
  134. package/installer/templates/project/.vscode/settings.json +30 -0
  135. package/installer/templates/project/README.md +58 -0
  136. package/installer/versions.env +110 -0
  137. package/package.json +16 -16
  138. package/src/as-built.ts +206 -0
  139. package/src/boardgen.ts +1428 -0
  140. package/src/chips/controllers.ts +61 -12
  141. package/src/chips/index.ts +35 -54
  142. package/src/chips/resolve.ts +316 -14
  143. package/src/chips/types.ts +388 -13
  144. package/src/display/profiles.ts +1 -1
  145. package/src/doctor.ts +110 -77
  146. package/src/dt-config/custom-board.ts +325 -0
  147. package/src/dt-config/kconfig.ts +177 -19
  148. package/src/dt-config/overlay.ts +1058 -450
  149. package/src/framework.manifest.ts +125 -178
  150. package/src/index.ts +57 -41
  151. package/src/lowering/adc.ts +222 -102
  152. package/src/lowering/ble.ts +32 -28
  153. package/src/lowering/dac.ts +82 -82
  154. package/src/lowering/fs.ts +18 -10
  155. package/src/lowering/gpio.ts +242 -152
  156. package/src/lowering/http.ts +512 -488
  157. package/src/lowering/hwtimer.ts +139 -101
  158. package/src/lowering/i2c.ts +116 -126
  159. package/src/lowering/index.ts +91 -93
  160. package/src/lowering/interrupts.ts +113 -21
  161. package/src/lowering/mqtt.ts +12 -6
  162. package/src/lowering/preferences.ts +327 -354
  163. package/src/lowering/pwm.ts +167 -74
  164. package/src/lowering/random.ts +3 -3
  165. package/src/lowering/sensor.ts +155 -0
  166. package/src/lowering/spi.ts +164 -124
  167. package/src/lowering/thread.ts +63 -0
  168. package/src/lowering/timing.ts +46 -72
  169. package/src/lowering/uart.ts +73 -51
  170. package/src/lowering/usb.ts +221 -0
  171. package/src/lowering/wdt.ts +95 -84
  172. package/src/lowering/wifi.ts +120 -111
  173. package/src/sdk/board-catalog-sync.ts +62 -0
  174. package/src/strategy.ts +888 -229
  175. package/src/toolchain/bossac-touch.ts +112 -0
  176. package/src/toolchain/compat.ts +8 -3
  177. package/src/toolchain/debug-config.ts +666 -522
  178. package/src/toolchain/env-check.ts +9 -9
  179. package/src/toolchain/index.ts +765 -44
  180. package/src/toolchain/scaffold.ts +81 -7
  181. package/src/toolchain/west-discover.ts +334 -316
  182. package/src/toolchain/west-spawn.ts +17 -5
  183. package/src/async/timer-polyfill.ts +0 -107
  184. package/src/chips/esp32.ts +0 -74
  185. package/src/chips/esp32s3.ts +0 -57
  186. package/src/chips/xiao-ble.ts +0 -70
  187. package/src/lowering/power.ts +0 -70
  188. package/src/lowering/pulse.ts +0 -93
  189. package/src/lowering/tone.ts +0 -60
  190. package/src/lowering/worker-backing.ts +0 -94
  191. package/src/lowering/worker.ts +0 -17
@@ -8,7 +8,7 @@
8
8
  // debug session is self-contained. cortex-debug starts OpenOCD as a child
9
9
  // process via `servertype: "openocd"`; the preLaunch task handles only the
10
10
  // build + flash step. After attach we issue `monitor reset init`, set a
11
- // temporary hardware breakpoint at setup(), and continue — this ensures the
11
+ // temporary hardware breakpoint at main(), and continue — this ensures the
12
12
  // breakpoint is deferred until the bootloader maps the app flash region.
13
13
  //
14
14
  // All generators are deterministic + idempotent so toggling --debug does not
@@ -17,6 +17,9 @@
17
17
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
18
18
  import { join, resolve, dirname, relative } from 'node:path';
19
19
  import { ZephyrStrategy } from '../strategy.js';
20
+ import { resolveChipFromBoard } from '../chips/resolve.js';
21
+ import { resolveProbeMethod } from './index.js';
22
+ import { loadCuttlefishConfig } from '@typecad/cuttlefish/config-loader';
20
23
  /**
21
24
  * Resolve the GDB binary path for the target from the build cache, falling
22
25
  * back to a filesystem scan of known Zephyr SDK locations when no build
@@ -29,14 +32,13 @@ import { ZephyrStrategy } from '../strategy.js';
29
32
  * omits gdbPath and relies on Cortex-Debug's default resolution).
30
33
  */
31
34
  export function resolveGdbPath(buildDir, target) {
32
- void target; // toolchain dir is esp32s3-specific today; see gdbPathFromSdkRoot
33
35
  const cachePath = join(buildDir, 'CMakeCache.txt');
34
36
  if (existsSync(cachePath)) {
35
37
  try {
36
38
  const cache = readFileSync(cachePath, 'utf-8');
37
39
  const m = cache.match(/^ZEPHYR_SDK_INSTALL_DIR:PATH=(.+)$/m);
38
40
  if (m) {
39
- const fromCache = gdbPathFromSdkRoot(m[1].trim());
41
+ const fromCache = gdbPathFromSdkRoot(m[1].trim(), target);
40
42
  if (fromCache)
41
43
  return fromCache;
42
44
  }
@@ -47,17 +49,23 @@ export function resolveGdbPath(buildDir, target) {
47
49
  }
48
50
  // No build dir yet (project just created): probe known SDK locations.
49
51
  for (const sdkRoot of discoverZephyrSdkRoots()) {
50
- const p = gdbPathFromSdkRoot(sdkRoot);
52
+ const p = gdbPathFromSdkRoot(sdkRoot, target);
51
53
  if (p)
52
54
  return p;
53
55
  }
54
56
  return undefined;
55
57
  }
56
- /** The esp32s3 xtensa GDB location inside a Zephyr SDK root (verified against
57
- * zephyr-sdk-0.17.4). Returns a forward-slash absolute path or undefined. */
58
- export function gdbPathFromSdkRoot(sdkRoot) {
59
- const gdbName = 'xtensa-espressif_esp32s3_zephyr-elf-gdb.exe';
60
- const gdbPath = join(sdkRoot, 'xtensa-espressif_esp32s3_zephyr-elf', 'bin', gdbName);
58
+ /** The GDB location inside a Zephyr SDK root, per target architecture:
59
+ * ARM boards use the arm-zephyr-eabi toolchain (verified against
60
+ * zephyr-sdk-0.17.4), Espressif the xtensa-espressif_esp32s3_zephyr-elf one.
61
+ * Returns a forward-slash absolute path or undefined. */
62
+ export function gdbPathFromSdkRoot(sdkRoot, target) {
63
+ // No target given (legacy callers/tests): the historical Espresif default.
64
+ const boardId = (target ?? '').split('/')[0];
65
+ const isArm = boardId.length > 0 && !boardId.startsWith('esp32');
66
+ const toolchainDir = isArm ? 'arm-zephyr-eabi' : 'xtensa-espressif_esp32s3_zephyr-elf';
67
+ const gdbName = isArm ? 'arm-zephyr-eabi-gdb.exe' : 'xtensa-espressif_esp32s3_zephyr-elf-gdb.exe';
68
+ const gdbPath = join(sdkRoot, toolchainDir, 'bin', gdbName);
61
69
  return existsSync(gdbPath) ? gdbPath.replace(/\\/g, '/') : undefined;
62
70
  }
63
71
  /** Compare two dotted version strings numerically (0.17.10 > 0.17.4). */
@@ -76,7 +84,7 @@ function compareSdkVersions(a, b) {
76
84
  * Probe the well-known Zephyr SDK install locations, newest version first:
77
85
  * 1. $ZEPHYR_SDK_INSTALL_DIR (the var board.cmake reads)
78
86
  * 2. <MAMBA_ROOT_PREFIX | ~/micromamba>/zephyr-sdk/zephyr-sdk-<ver> — the
79
- * @typecad/zephyr-installer layout
87
+ * the bundled Zephyr installer layout
80
88
  * 3. ~/zephyr-sdk-<ver> — the standalone download layout
81
89
  *
82
90
  * Only roots that actually contain the esp32s3 GDB are useful to callers;
@@ -116,44 +124,59 @@ export function discoverZephyrSdkRoots(opts) {
116
124
  return roots.filter((r, i) => roots.indexOf(r) === i);
117
125
  }
118
126
  /**
119
- * Resolve the Espressif OpenOCD binary path. The esp32s3 needs the Espressif
120
- * OpenOCD fork (openocd-esp32) — not the Zephyr SDK's openocd and not a
127
+ * Resolve the OpenOCD binary path. The esp32s3 needs the Espressif OpenOCD
128
+ * fork (openocd-esp32) — not the Zephyr SDK's openocd and not a
121
129
  * generic/GDB-stub build — because only it carries the Xtensa + esp_usb_jtag
122
- * support. It is NOT on PATH by default, so Cortex-Debug must be pointed at it
123
- * explicitly or it fails with `spawn openocd.exe ENOENT`.
130
+ * support. ARM targets prefer the Zephyr SDK's own openocd (it ships the
131
+ * interface/target cfgs boards reference); the Espressif fork is only
132
+ * consulted for esp32 targets.
124
133
  *
125
134
  * Discovery order:
126
- * 1. ESPRESSIF_TOOLCHAIN_PATH env (the var board.cmake reads) — if set, its
127
- * openocd-esp32/bin/openocd.exe.
135
+ * 1. ESPRESSIF_TOOLCHAIN_PATH env (the var board.cmake reads) — esp32
136
+ * targets; its openocd-esp32/bin/openocd.exe.
128
137
  * 2. The standard ESP-IDF install layout: ~/.espressif/tools/openocd-esp32/
129
- * <version>/openocd-esp32/bin/openocd.exe. Pick the newest version dir.
138
+ * <version>/openocd-esp32/bin/openocd.exe (esp32 targets; newest first).
139
+ * 3. A Zephyr SDK root (discoverZephyrSdkRoots) — its
140
+ * tools/opt/openocd/bin/openocd(.exe), any target.
130
141
  * Returns undefined if not found (the launch.json then omits openOCDPath and
131
142
  * Cortex-Debug falls back to PATH / its openocdPath setting).
132
143
  */
133
- export function resolveOpenOcdPath() {
144
+ export function resolveOpenOcdPath(target) {
145
+ const isEsp32Target = (target ?? '').split('/')[0].startsWith('esp32');
134
146
  const candidates = [];
135
- // 1. ESPRESSIF_TOOLCHAIN_PATH env
136
- const envPath = process.env.ESPRESSIF_TOOLCHAIN_PATH;
137
- if (envPath) {
138
- candidates.push(join(envPath, 'openocd-esp32', 'bin', 'openocd.exe'));
139
- }
140
- // 2. ~/.espressif/tools/openocd-esp32/<version>/openocd-esp32/bin/openocd.exe
141
- const home = process.env.USERPROFILE || process.env.HOME;
142
- if (home) {
143
- const base = join(home, '.espressif', 'tools', 'openocd-esp32');
144
- let versions = [];
145
- try {
146
- versions = readdirSync(base).filter((v) => existsSync(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe')));
147
- }
148
- catch {
149
- // dir absent
147
+ if (isEsp32Target) {
148
+ // 1. ESPRESSIF_TOOLCHAIN_PATH env
149
+ const envPath = process.env.ESPRESSIF_TOOLCHAIN_PATH;
150
+ if (envPath) {
151
+ candidates.push(join(envPath, 'openocd-esp32', 'bin', 'openocd.exe'));
150
152
  }
151
- // newest version last — sort then reverse so the highest wins on match.
152
- versions.sort().reverse();
153
- for (const v of versions) {
154
- candidates.push(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe'));
153
+ // 2. ~/.espressif/tools/openocd-esp32/<version>/openocd-esp32/bin/openocd.exe
154
+ const home = process.env.USERPROFILE || process.env.HOME;
155
+ if (home) {
156
+ const base = join(home, '.espressif', 'tools', 'openocd-esp32');
157
+ let versions = [];
158
+ try {
159
+ versions = readdirSync(base).filter((v) => existsSync(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe')));
160
+ }
161
+ catch {
162
+ // dir absent
163
+ }
164
+ // newest version last — sort then reverse so the highest wins on match.
165
+ versions.sort().reverse();
166
+ for (const v of versions) {
167
+ candidates.push(join(base, v, 'openocd-esp32', 'bin', 'openocd.exe'));
168
+ }
155
169
  }
156
170
  }
171
+ // 3. The Zephyr SDK's openocd (ARM targets' interface/target cfgs ship
172
+ // with it — interface/stlink-dap.cfg, target/stm32f4x.cfg, ...). Layout
173
+ // differs by SDK generation: 1.0.x hosttools/openocd, 0.17.x
174
+ // tools/opt/openocd (when present).
175
+ const exe = process.platform === 'win32' ? 'openocd.exe' : 'openocd';
176
+ for (const sdkRoot of discoverZephyrSdkRoots()) {
177
+ candidates.push(join(sdkRoot, 'hosttools', 'openocd', 'bin', exe));
178
+ candidates.push(join(sdkRoot, 'tools', 'opt', 'openocd', 'bin', exe));
179
+ }
157
180
  for (const c of candidates) {
158
181
  if (existsSync(c))
159
182
  return resolve(c).replace(/\\/g, '/');
@@ -173,8 +196,8 @@ export function resolveOpenOcdPath() {
173
196
  * THAT as the workspace root. This makes F5 work when a user opens the project
174
197
  * folder directly, and keeps launch.json paths relative to it.
175
198
  *
176
- * Returns { workspaceRoot, sketchRel } where workspaceRoot is the cuttlefish
177
- * project root (the `.vscode/` target) and sketchRel is the Zephyr app dir
199
+ * Returns { workspaceRoot, appRel } where workspaceRoot is the cuttlefish
200
+ * project root (the `.vscode/` target) and appRel is the Zephyr app dir
178
201
  * (`projectRoot`) relative to it (e.g. 'src/out').
179
202
  */
180
203
  export function resolveDebugLocations(projectRoot) {
@@ -193,8 +216,8 @@ export function resolveDebugLocations(projectRoot) {
193
216
  break;
194
217
  dir = parent;
195
218
  }
196
- const sketchRel = relative(workspaceRoot, resolve(projectRoot)).replace(/\\/g, '/');
197
- return { workspaceRoot, sketchRel };
219
+ const appRel = relative(workspaceRoot, resolve(projectRoot)).replace(/\\/g, '/');
220
+ return { workspaceRoot, appRel };
198
221
  }
199
222
  /** Read-merge-write a JSON file, adding/replacing a single config by a key. */
200
223
  function mergeJsonArrayEntry(filePath, arrayKey, matchKey, entry) {
@@ -277,10 +300,10 @@ export function generateGdbScript(sourceMapPath) {
277
300
  * `openOcdCfgRel` is the workspace-relative path to the generated
278
301
  * .cuttlefish/openocd.cfg, passed to cortex-debug's configFiles.
279
302
  */
280
- function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel) {
303
+ function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel, method) {
281
304
  // The ELF is at <projectRoot>/build/zephyr/zephyr.elf (Zephyr's standard
282
305
  // build output). cortex-debug uses `executable` (not `program`).
283
- const executable = `\${workspaceFolder}/${o.sketchRel}/build/zephyr/zephyr.elf`;
306
+ const executable = `\${workspaceFolder}/${o.appRel}/build/zephyr/zephyr.elf`;
284
307
  // OpenOCD cfg relative to the workspace root so cortex-debug can pass it
285
308
  // via the -f flag. Must be a list; cortex-debug prepends -f per entry.
286
309
  const configFiles = [`\${workspaceFolder}/${openOcdCfgRel}`];
@@ -289,7 +312,7 @@ function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel) {
289
312
  // OpenOCD binary path — the Espressif fork (openocd-esp32) is required for
290
313
  // the esp_usb_jtag adapter. cortex-debug's `serverpath` tells it where to
291
314
  // find the binary (not on PATH by default).
292
- const openocdPath = resolveOpenOcdPath();
315
+ const openocdPath = resolveOpenOcdPath(o.target);
293
316
  // Post-attach commands executed after GDB connects to the OpenOCD gdbserver.
294
317
  // set mem inaccessible-by-default off — suppresses "Cannot access memory"
295
318
  // errors caused by overlapping Xtensa memory regions (flash-mapped
@@ -298,41 +321,62 @@ function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel) {
298
321
  // read-only so -break-insert uses hw breakpoints, not sw breakpoints
299
322
  // (which would fail with "Cannot access memory at 0x4200xxxx").
300
323
  // monitor reset init — reset target + halt (bootloader maps flash)
301
- // thb setup — temporary HW breakpoint at setup()
302
- // c — continue; bootloader maps flash, breaks at setup()
324
+ // thb main — temporary HW breakpoint at main()
325
+ // c — continue; bootloader maps flash, breaks at main()
326
+ //
327
+ // The trailing `c` is ESP32-only. On instant-reset ARM targets the
328
+ // thb-main stop lands within milliseconds — WHILE cortex-debug is still
329
+ // chewing through this command list — and a stop event mid-initialization
330
+ // leaves the session half-started (toolbar never enables, user breakpoints
331
+ // never bind). The ESP32's bootloader takes hundreds of milliseconds, so
332
+ // its stop safely arrives after init completes. ARM keeps the pending
333
+ // thb (the first user Continue stops at main()) but hands the run/stop
334
+ // transition to cortex-debug.
303
335
  //
304
336
  // Paths use forward slashes — ${workspaceFolder} on Windows produces
305
337
  // backslashes that GDB interprets as escape sequences (\t → tab, etc.).
306
338
  const ws = o.workspaceRoot.replace(/\\/g, '/');
339
+ // The Xtensa flash-mapping commands are ESP32-specific (app flash isn't
340
+ // readable until the bootloader maps it); ARM targets drop them.
341
+ const isEsp32Target = o.target.split('/')[0].startsWith('esp32');
307
342
  const postAttachCommands = [
308
343
  `set directories ${ws}`,
309
344
  'set remote hardware-watchpoint-limit 2',
310
345
  'set remote hardware-breakpoint-limit 2',
311
- 'set mem inaccessible-by-default off',
312
- 'mem 0x42000000 0x44000000 ro cache',
346
+ ...(isEsp32Target ? [
347
+ 'set mem inaccessible-by-default off',
348
+ 'mem 0x42000000 0x44000000 ro cache',
349
+ ] : []),
313
350
  'monitor reset init',
314
- 'thb setup',
315
- 'c',
351
+ 'thb main',
352
+ ...(isEsp32Target ? ['c'] : []),
316
353
  ];
317
354
  if (gdbScriptRel) {
318
355
  postAttachCommands.splice(1, 0, `source ${ws}/${gdbScriptRel}`);
319
356
  }
357
+ // Probe-method-driven server shape: jlink-runner methods use cortex-debug's
358
+ // jlink server (needs the device name from the board table); openocd-runner
359
+ // methods keep the openocd server with the generated cfg file. Without a
360
+ // method (ESP32 builtin-JTAG targets), the historical default applies.
361
+ const servertype = method?.runner === 'jlink' ? 'jlink' : 'openocd';
362
+ const isEsp32 = o.target.split('/')[0].startsWith('esp32');
320
363
  const cfg = {
321
- name: 'TypeCAD Debug (Zephyr, ESP32-S3)',
364
+ name: `TypeCAD Debug (Zephyr, ${o.target.split('/')[0]})`,
322
365
  type: 'cortex-debug',
323
366
  // Attach mode: no download (the ELF is already flashed). The server
324
367
  // controller's attachCommands() just halts the target, then our
325
- // postAttachCommands reset it, set a HW breakpoint at setup(), and
368
+ // postAttachCommands reset it, set a HW breakpoint at main(), and
326
369
  // continue. HW breakpoints use debug registers and work before the
327
370
  // bootloader maps the app flash region.
328
371
  request: 'attach',
329
372
  cwd: '${workspaceFolder}',
330
373
  executable,
331
- servertype: 'openocd',
332
- configFiles,
333
- interface: 'jtag',
374
+ servertype,
375
+ ...(servertype === 'openocd' ? { configFiles } : {}),
376
+ ...(method?.debugDevice ? { device: method.debugDevice } : {}),
377
+ interface: method?.debugInterface ?? (isEsp32 ? 'jtag' : 'swd'),
334
378
  ...(gdbPath ? { gdbPath } : {}),
335
- ...(openocdPath ? { serverpath: openocdPath } : {}),
379
+ ...(servertype === 'openocd' && openocdPath ? { serverpath: openocdPath } : {}),
336
380
  postAttachCommands,
337
381
  preLaunchTask: 'cuttlefish: build + flash (debug)',
338
382
  };
@@ -351,7 +395,7 @@ function buildTask(o) {
351
395
  label: 'cuttlefish: build + flash (debug)',
352
396
  type: 'shell',
353
397
  command: 'npx cuttlefish build --compile --upload --debug',
354
- options: { cwd: `\${workspaceFolder}/${o.sketchRel}` },
398
+ options: { cwd: `\${workspaceFolder}/${o.appRel}` },
355
399
  group: { kind: 'build', isDefault: false },
356
400
  problemMatcher: [],
357
401
  };
@@ -378,13 +422,77 @@ const OPENOCD_ADAPTER_SPEED = 4000;
378
422
  * helper/RTOS tcl, so the override can land before the driver exists or be
379
423
  * re-defaulted). Putting it in the cfg, after the source, is deterministic.
380
424
  */
381
- function buildOpenOcdCfg() {
382
- return [
425
+ /**
426
+ * Drop the gdb-attach/gdb-detach target-event blocks from a cfg line list.
427
+ * Board cfgs use them for standalone openocd sessions (reset on attach,
428
+ * resume on detach), but under VS Code they fire DURING cortex-debug's
429
+ * initialization — the unexpected stop event aborts the session setup
430
+ * ("Program stopped, probably due to a reset and/or halt issued by
431
+ * debugger") and the toolbar never enables. The IDE owns the lifecycle.
432
+ */
433
+ function stripGdbEventBlocks(lines) {
434
+ const out = [];
435
+ let skipping = false;
436
+ let depth = 0;
437
+ for (const line of lines) {
438
+ if (!skipping && /configure\s+-event\s+gdb-(attach|detach)/.test(line)) {
439
+ skipping = true;
440
+ depth = 0;
441
+ }
442
+ if (skipping) {
443
+ depth += (line.match(/\{/g) ?? []).length - (line.match(/\}/g) ?? []).length;
444
+ if (depth <= 0 && (line.includes('}') || !line.includes('{')))
445
+ skipping = false;
446
+ continue;
447
+ }
448
+ out.push(line);
449
+ }
450
+ return out;
451
+ }
452
+ function buildOpenOcdCfg(method, target) {
453
+ const header = [
383
454
  '# Auto-generated by @typecad/framework-zephyr. Do not edit — regenerate',
384
- '# with `cuttlefish build --debug`. Sources the board cfg (which loads the',
385
- '# esp_usb_jtag adapter driver + esp32s3 target + ESP_RTOS Zephyr) then',
386
- '# overrides the adapter speed to a USB-JTAG-stable value AFTER the driver',
387
- '# is loaded. See debug-config.ts for the rationale.',
455
+ '# with `cuttlefish build --debug`.',
456
+ ];
457
+ // Zephyr RTOS awareness (thread names in the call stack) appended after
458
+ // the target source on ARM targets; the ESP32 board cfgs already carry
459
+ // ESP_RTOS Zephyr.
460
+ const isEsp32 = (target ?? '').split('/')[0].startsWith('esp32');
461
+ const rtosLine = isEsp32 ? [] : ['$_TARGETNAME configure -rtos Zephyr'];
462
+ if (method?.debugCfgSource && method.debugCfgSource.length > 0) {
463
+ // Probe-method-driven config: the board package's verified interface +
464
+ // target sources, then the method's quirk lines (e.g. reset_config for an
465
+ // unwired SRST) — the cfg-file form of the west runner args.
466
+ return [
467
+ ...header,
468
+ `# Probe method '${method.id}' — from the board package's probeMethods table.`,
469
+ ...method.debugCfgSource.map((src) => `source [find ${src}]`),
470
+ ...stripGdbEventBlocks(method.debugCfg ?? []),
471
+ ...rtosLine,
472
+ '',
473
+ ].join('\n');
474
+ }
475
+ // Pack-derived methods (the board's own support/openocd.cfg, extracted by
476
+ // the board data pack) carry complete cfg lines — including their
477
+ // `source [find ...]` directives — verbatim in file order, minus the
478
+ // gdb-attach/detach events (IDE-managed lifecycle; see stripGdbEventBlocks).
479
+ if (method?.debugCfg && method.debugCfg.length > 0) {
480
+ return [
481
+ ...header,
482
+ `# Probe method '${method.id}' — the board's own support/openocd.cfg,`,
483
+ `# extracted by the board data pack (gdb-attach/detach events removed —`,
484
+ `# the IDE owns the session lifecycle).`,
485
+ ...stripGdbEventBlocks(method.debugCfg),
486
+ ...rtosLine,
487
+ '',
488
+ ].join('\n');
489
+ }
490
+ return [
491
+ ...header,
492
+ '# Sources the board cfg (which loads the esp_usb_jtag adapter driver +',
493
+ '# esp32s3 target + ESP_RTOS Zephyr) then overrides the adapter speed to a',
494
+ '# USB-JTAG-stable value AFTER the driver is loaded. See debug-config.ts',
495
+ '# for the rationale.',
388
496
  'source [find board/esp32s3-builtin.cfg]',
389
497
  `adapter speed ${OPENOCD_ADAPTER_SPEED}`,
390
498
  '',
@@ -400,25 +508,57 @@ function buildOpenOcdCfg() {
400
508
  * <projectRoot>/.cuttlefish/openocd.cfg (OpenOCD cfg, adapter speed override)
401
509
  * <projectRoot>/.cuttlefish/.cuttlefish-gdb.py (lambda frame filter, conditional)
402
510
  */
511
+ /**
512
+ * Resolve the debug-side probe method for a project: the board constants the
513
+ * transpile persists + the workspace config's zephyr.probe selection.
514
+ * Returns the method object (for cfg/servertype data) or undefined — ESP32
515
+ * targets and boards without tables fall back to the framework's default
516
+ * (esp_usb_jtag openocd) behavior.
517
+ */
518
+ function resolveDebugProbeMethod(projectRoot, workspaceRoot) {
519
+ try {
520
+ const bcPath = [
521
+ join(projectRoot, 'src', 'board-constants.json'),
522
+ join(projectRoot, 'board-constants.json'),
523
+ ].find((p) => existsSync(p));
524
+ if (!bcPath)
525
+ return undefined;
526
+ const raw = JSON.parse(readFileSync(bcPath, 'utf8'));
527
+ const chip = resolveChipFromBoard(new Map(Object.entries(raw)));
528
+ if (!chip)
529
+ return undefined;
530
+ const zc = loadCuttlefishConfig(workspaceRoot)?.zephyrConfig;
531
+ const probe = resolveProbeMethod(zc, chip, 'debug');
532
+ if (!probe.ok)
533
+ return undefined;
534
+ return chip.probeMethods?.find((m) => m.id === zc?.probe);
535
+ }
536
+ catch {
537
+ return undefined;
538
+ }
539
+ }
403
540
  export function writeDebugConfig(o) {
404
541
  const vscodeDir = join(o.workspaceRoot, '.vscode');
405
542
  const cuttlefishDir = join(o.projectRoot, '.cuttlefish');
406
543
  mkdirSync(cuttlefishDir, { recursive: true });
544
+ // The selected probe method (if any) drives the debug server shape:
545
+ // openocd cfg source/quirks, cortex-debug servertype, wire interface.
546
+ const method = resolveDebugProbeMethod(o.projectRoot, o.workspaceRoot);
407
547
  // openocd.cfg — written first so its relative path can be wired into the
408
548
  // cortex-debug configFiles. cortex-debug starts OpenOCD as a child process
409
549
  // and passes this file via -f.
410
550
  const openOcdCfgPath = join(cuttlefishDir, 'openocd.cfg');
411
- writeFileSync(openOcdCfgPath, buildOpenOcdCfg(), 'utf-8');
412
- const openOcdCfgRel = `${o.sketchRel}/.cuttlefish/openocd.cfg`;
551
+ writeFileSync(openOcdCfgPath, buildOpenOcdCfg(method, o.target), 'utf-8');
552
+ const openOcdCfgRel = `${o.appRel}/.cuttlefish/openocd.cfg`;
413
553
  // launch.json — merge the cortex-debug config by name.
414
554
  const gdbScript = generateGdbScript(o.sourceMapPath);
415
555
  let gdbScriptRel;
416
556
  if (gdbScript) {
417
557
  const scriptPath = join(cuttlefishDir, '.cuttlefish-gdb.py');
418
558
  writeFileSync(scriptPath, gdbScript, 'utf-8');
419
- gdbScriptRel = `${o.sketchRel}/.cuttlefish/.cuttlefish-gdb.py`;
559
+ gdbScriptRel = `${o.appRel}/.cuttlefish/.cuttlefish-gdb.py`;
420
560
  }
421
- const launchConfig = buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel);
561
+ const launchConfig = buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel, method);
422
562
  mergeJsonArrayEntry(join(vscodeDir, 'launch.json'), 'configurations', 'name', launchConfig);
423
563
  // tasks.json — merge the build+flash task by label. OpenOCD is managed by
424
564
  // cortex-debug so the task is a simple synchronous build step.
@@ -431,9 +571,9 @@ export function writeDebugConfig(o) {
431
571
  * the CLI resolves output.outDir against the ENTRY's directory (cli.ts), so
432
572
  * the emitted app root — and therefore the ELF, build dir, and .cuttlefish/
433
573
  * debug artifacts — always lands at `src/out`. Keep in sync with
434
- * generateProjectConfig in @typecad/cuttlefish create/init-templates.ts.
574
+ * generateProjectConfig in @typecad/cuttlefish create/templates.ts.
435
575
  */
436
- const STARTER_SKETCH_REL = 'src/out';
576
+ const STARTER_APP_REL = 'src/out';
437
577
  /**
438
578
  * Create-time starter debug artifacts. Called by the cuttlefish `create` flow
439
579
  * (via the package's `writeProjectDebugArtifacts` export) so a fresh project
@@ -453,11 +593,11 @@ export function writeProjectDebugArtifacts(o) {
453
593
  if (new ZephyrStrategy().debugMode(o.buildTarget) !== 'gdb')
454
594
  return [];
455
595
  const workspaceRoot = resolve(o.workspaceRoot);
456
- const projectRoot = join(workspaceRoot, STARTER_SKETCH_REL);
596
+ const projectRoot = join(workspaceRoot, STARTER_APP_REL);
457
597
  writeDebugConfig({
458
598
  projectRoot,
459
599
  workspaceRoot,
460
- sketchRel: STARTER_SKETCH_REL,
600
+ appRel: STARTER_APP_REL,
461
601
  target: o.buildTarget ?? '',
462
602
  // No build dir exists yet — resolveGdbPath falls back to probing known
463
603
  // Zephyr SDK locations so gdbPath is still filled in when possible.
@@ -466,6 +606,6 @@ export function writeProjectDebugArtifacts(o) {
466
606
  return [
467
607
  '.vscode/launch.json',
468
608
  '.vscode/tasks.json',
469
- `${STARTER_SKETCH_REL}/.cuttlefish/openocd.cfg`,
609
+ `${STARTER_APP_REL}/.cuttlefish/openocd.cfg`,
470
610
  ];
471
611
  }
@@ -2,7 +2,7 @@ import { type WestInstall } from './west-discover.js';
2
2
  import { type CompatStatus } from './compat.js';
3
3
  /**
4
4
  * Raw west facts gathered from discovery + a `west --version` probe. Mirrors
5
- * ArduinoCliProbeData: `westFound` is true when a usable west install was
5
+ * probe data: `westFound` is true when a usable west install was
6
6
  * discovered (discovery itself probes responsiveness).
7
7
  */
8
8
  export interface WestProbeData {
@@ -16,8 +16,8 @@ export interface WestProbeData {
16
16
  zephyrBase: string | undefined;
17
17
  }
18
18
  /**
19
- * Test-injection seam for checkZephyrEnv. Mirrors Arduino's
20
- * CheckArduinoEnvOptions.fakeProbe so tests never spawn a real west/python.
19
+ * Test-injection seam for checkZephyrEnv. Mirrors the
20
+ * fakeProbe option so tests never spawn a real west/python.
21
21
  */
22
22
  export interface CheckZephyrEnvOptions {
23
23
  /** FOR TESTS ONLY: skip the real probe and use this data directly. */
@@ -84,7 +84,7 @@ export declare function boardExistsInCheckout(boardId: string, zephyrBase: strin
84
84
  * checkout. Reports what (if anything) is wrong.
85
85
  *
86
86
  * - If `buildTarget` is undefined/empty, the board check is skipped (not a
87
- * failure), mirroring Arduino's no-FQBN path.
87
+ * failure), mirroring the no-build-target path.
88
88
  * - Never installs anything. Never mutates the user environment.
89
89
  * - Never throws — always returns a result. Callers decide how to react.
90
90
  *
@@ -1,15 +1,15 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Zephyr environment check — the shared detection behind `cuttlefish doctor`.
3
3
  //
4
- // Mirrors @typecad/arduino-cli's checkArduinoEnv(): gather the impure
4
+ // Zephyr environment check: gather the impure
5
5
  // environment facts once (west presence + version, Zephyr version, board
6
6
  // existence), then reduce them to a structured result the doctor (and, later,
7
7
  // the build/test gates) can present uniformly. The check is side-effect-free
8
8
  // and never throws — it never installs or mutates anything.
9
9
  //
10
- // Two parity checks vs. framework-arduino's doctor:
10
+ // Two checks (mirroring the doctor contract):
11
11
  // 1. west (the Zephyr build tool) is discoverable + responsive — the direct
12
- // analog of "arduino-cli is installed". discoverWest() already confirms
12
+ // toolchain presence. discoverWest() already confirms
13
13
  // responsiveness via `west --version`; we additionally capture the version
14
14
  // string to report it.
15
15
  // 2. the configured board target exists in the Zephyr checkout
@@ -117,7 +117,7 @@ export function boardExistsInCheckout(boardId, zephyrBase) {
117
117
  * checkout. Reports what (if anything) is wrong.
118
118
  *
119
119
  * - If `buildTarget` is undefined/empty, the board check is skipped (not a
120
- * failure), mirroring Arduino's no-FQBN path.
120
+ * failure), mirroring the no-build-target path.
121
121
  * - Never installs anything. Never mutates the user environment.
122
122
  * - Never throws — always returns a result. Callers decide how to react.
123
123
  *
@@ -165,7 +165,7 @@ export function checkZephyrEnv(buildTarget, options) {
165
165
  check,
166
166
  messages: [
167
167
  `Zephyr ${zephyrVersion} is outside the supported range (${compat.range}) for @typecad/framework-zephyr.`,
168
- " Set ZEPHYR_BASE to a compatible Zephyr checkout, or install one via '@typecad/zephyr-installer'.",
168
+ " Set ZEPHYR_BASE to a compatible Zephyr checkout, or or run the bundled Zephyr installer (npx --package @typecad/framework-zephyr zephyr-installer).",
169
169
  ],
170
170
  fixCommand: undefined,
171
171
  };
@@ -1,4 +1,32 @@
1
1
  import type { ToolchainOptions, CompileResult, UploadResult } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Distinct DT-bound sensors the emitted sources reference — the lowering's
5
+ * state blocks name each one `__tc_sensor_<part>_i2c<N>_0x<addr>_dev`
6
+ * (lowering/sensor.ts sensorNames), so var-presence is the authoritative
7
+ * signal. Feeds the overlay's DT child nodes; the part group is greedy so a
8
+ * compatible containing '_i2c<N>_' still resolves to the longest part match.
9
+ */
10
+ export interface ScannedSensorPart {
11
+ part: string;
12
+ busIndex: number;
13
+ port: number;
14
+ busKind: 'i2c' | 'spi';
15
+ spiHz: number;
16
+ spiMode: number;
17
+ alertPin: number;
18
+ }
19
+ export interface ScannedSpiTarget {
20
+ busIndex: number;
21
+ cs: number;
22
+ hz: number;
23
+ mode: number;
24
+ }
25
+ /** Scan the emitted source for thin SPI targets (hal/spi-target.ts): the
26
+ * spi_dt_spec state block's tc-spit-cfg comment carries the construction
27
+ * facts, the same channel tc-sensor-cfg uses. */
28
+ export declare function scanSpiTargets(src: string): ScannedSpiTarget[];
29
+ export declare function scanSensorParts(src: string): ScannedSensorPart[];
2
30
  /**
3
31
  * Derive the Zephyr project root from the cuttlefish-emitted source path.
4
32
  *
@@ -26,7 +54,44 @@ export declare function projectRootFromOptions(o: ToolchainOptions): string;
26
54
  * Exported (pure) so the runner-selection contract is unit-testable without
27
55
  * spawning west.
28
56
  */
29
- export declare function buildFlashArgs(buildDir: string, board: string, userRunner: string | undefined, port: string | undefined): string[];
57
+ /**
58
+ * Resolve HOW this build attaches to the board for flashing OR debugging:
59
+ * the friendly `zephyr.probe` id from the board's probeMethods table (quirks
60
+ * included), or the raw `zephyr.runner` escape hatch. Exported (pure) so the
61
+ * selection contract is unit-testable without spawning west.
62
+ *
63
+ * Rules:
64
+ * - `probe` + `runner` together is an error (two ways of saying it — pick one).
65
+ * - An unknown `probe` id is an error listing what the board supports; a board
66
+ * with no probeMethods table gets a hint to use `runner` directly.
67
+ * - purpose 'debug': the chosen method must be debug-capable (`debug` is not
68
+ * false — a bootloader is not a debugger). Non-capable or unknown ids list
69
+ * the debug-capable methods.
70
+ * - User `runnerArgs` are appended AFTER the method's args, so they can
71
+ * override the method's baked-in flags (argparse takes the last value).
72
+ */
73
+ export type ProbeResolution = {
74
+ ok: true;
75
+ runner?: string;
76
+ args: string[];
77
+ } | {
78
+ ok: false;
79
+ error: string;
80
+ };
81
+ export declare function resolveProbeMethod(zc: Record<string, unknown> | undefined, chip: ZephyrChipDescriptor, purpose?: 'flash' | 'debug'): ProbeResolution;
82
+ /**
83
+ * Whether a flash runner carries the upload over a serial port. Runner-gated,
84
+ * never board-name-gated: esptool and bossac are the only runners
85
+ * `buildFlashArgs` forwards `--port` to, so they are the only ones that
86
+ * cannot flash without one. Probe runners (openocd, jlink) and USB flows
87
+ * (dfu-util, uf2 mass storage) need no port — a missing `--port` must not
88
+ * block them.
89
+ *
90
+ * Exported (pure) so the port-requirement contract is unit-testable without
91
+ * spawning west.
92
+ */
93
+ export declare function uploadRequiresPort(runner: string | undefined): boolean;
94
+ export declare function buildFlashArgs(buildDir: string, userRunner: string | undefined, port: string | undefined, flashRunner?: string, runnerArgs?: readonly string[]): string[];
30
95
  /**
31
96
  * Classify a `west flash` result as success/failure.
32
97
  *