@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -304,7 +304,7 @@ For the CURRENT session without a new shell:
304
304
  eval "\$("$MAMBA" shell hook --shell bash)"
305
305
  micromamba activate $ENV_NAME
306
306
 
307
- Then a typeCAD/cuttlefish Zephyr build should discover west automatically:
308
- cd <project> && npx cuttlefish build
307
+ Then a typeCAD/typecad-hal Zephyr build should discover west automatically:
308
+ cd <project> && npx typecad-hal build
309
309
 
310
310
  EOF
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # Activates the micromamba env created by the typeCAD Zephyr installer. After
8
8
  # activation, `west` is on PATH and ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR point
9
- # at the new install, so `npx cuttlefish build` uses it automatically.
9
+ # at the new install, so `npx typecad-hal build` uses it automatically.
10
10
  #
11
11
  # Override the env name with $TYPECAD_ZEPHYR_ENV (default: "zephyr").
12
12
  # ---------------------------------------------------------------------------
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # Activates the micromamba env created by the typeCAD Zephyr installer. After
9
9
  # activation, `west` is on PATH and ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR point
10
- # at the new install, so `npx cuttlefish build` uses it automatically.
10
+ # at the new install, so `npx typecad-hal build` uses it automatically.
11
11
  #
12
12
  # Override the env name with $TYPECAD_ZEPHYR_ENV (default: "zephyr").
13
13
  # ---------------------------------------------------------------------------
@@ -5,7 +5,7 @@
5
5
  // .typecad/activate-zephyr.ps1 (-NoExit keeps the shell open afterward). This
6
6
  // makes `micromamba activate zephyr` happen automatically, so `west` is on
7
7
  // PATH and ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR are set without per-session
8
- // typing - and `npx cuttlefish build` uses the new install.
8
+ // typing - and `npx typecad-hal build` uses the new install.
9
9
  //
10
10
  // If this project already has a .vscode/settings.json, MERGE these keys into
11
11
  // it by hand rather than overwriting the whole file.
@@ -1,9 +1,9 @@
1
1
  # typeCAD Zephyr — per-project activation template
2
2
 
3
- Copy these files into a cuttlefish Zephyr project so that opening a terminal in
3
+ Copy these files into a typecad-hal Zephyr project so that opening a terminal in
4
4
  it auto-activates the micromamba env created by the typeCAD Zephyr installer.
5
5
  Once active, `west` is on PATH and `ZEPHYR_BASE` / `ZEPHYR_SDK_INSTALL_DIR` are
6
- set, so `npx cuttlefish build` uses the new install automatically — no
6
+ set, so `npx typecad-hal build` uses the new install automatically — no
7
7
  per-session `micromamba activate` typing.
8
8
 
9
9
  ## Files
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@typecad/framework-zephyr",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.19",
4
4
  "description": "TypeCAD framework package for the Zephyr RTOS \u2014 west/CMake build, devicetree-driven GPIO",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
+ "./package.json": "./package.json",
9
10
  ".": {
10
11
  "types": "./dist/index.d.ts",
11
12
  "default": "./dist/index.js"
@@ -38,14 +39,13 @@
38
39
  "dry-run": "node installer/install.mjs --dry-run"
39
40
  },
40
41
  "dependencies": {
41
- "@typecad/cuttlefish": "1.0.0-alpha.16",
42
- "@typecad/hal": "1.0.0-alpha.16"
42
+ "@typecad/cuttlefish": "1.0.0-alpha.19"
43
43
  },
44
44
  "devDependencies": {
45
- "@typecad/expect": "1.0.0-alpha.16",
45
+ "@typecad/hal": "1.0.0-alpha.19",
46
46
  "typescript": "^5.7.3"
47
47
  },
48
- "license": "MIT",
48
+ "license": "Apache-2.0",
49
49
  "publishConfig": {
50
50
  "access": "public"
51
51
  },
package/src/as-built.ts CHANGED
@@ -1,206 +1,206 @@
1
- // ---------------------------------------------------------------------------
2
- // as-built.ts — harvest pin routes from a build's resolved zephyr.dts
3
- //
4
- // The third fact source: after any successful `west build`, the fully
5
- // merged devicetree at <build>/zephyr/zephyr.dts contains the board's
6
- // pinctrl nodes with LABELS verbatim — the stable name grammar
7
- // (`adc1_in1_pa1`, `tim4_ch1_pb6`, `iomuxc_<pad>_adc1_in1`) — with the
8
- // vendor macro values already expanded away. Parsing names only needs ONE
9
- // grammar per shape, immune to vendor macro churn, and corrects a broken
10
- // catalog harvest per-pin before the next build (the walker's macro-value
11
- // grammars are the fragile half; this is the robust half).
12
- //
13
- // Scope is honest: only node-name families appear (STM32-style dtsi nodes
14
- // and the i.MX RT label grammar with its in-band pad join). Header-matrix
15
- // families (Kinetis/LPC/GD32 macro routing, ESP32/RP2 matrices) never
16
- // exist as DT nodes — the walker + family tables remain their source.
17
- // ----------------------------------------------------------------------------
18
-
19
- export interface AsBuiltRoute {
20
- source: string;
21
- channel: number;
22
- port: string;
23
- bit: number;
24
- pinctrl: string;
25
- }
26
-
27
- export interface AsBuiltFacts {
28
- adc: AsBuiltRoute[];
29
- pwm: AsBuiltRoute[];
30
- dac: AsBuiltRoute[];
31
- }
32
-
33
- /** The .cuttlefish/as-built.json shape (machine-authored facts file). */
34
- export interface AsBuiltFile {
35
- version: 1;
36
- board: string;
37
- generatedAt: string;
38
- routes: {
39
- adcPins: AsBuiltRoute[];
40
- pwmPins: AsBuiltRoute[];
41
- dacPins: AsBuiltRoute[];
42
- };
43
- }
44
-
45
- // STM32-style pinctrl dtsi labels (all spellings the walker knows):
46
- // adc1_in1_pa1 / adc_in0_pa0 / adc1_inp16_pa0, tim4_ch1_pb6,
47
- // dac1_out1_pa4. Labels appear verbatim in zephyr.dts
48
- // (`adc1_in0_pa0: adc1_in0_pa0 {`). The `inn` negative inputs stay out —
49
- // the thin HAL is single-ended. The F1 (AFIO) family uses `tim1_ch1_pwm_out_pa8`
50
- // and `dac_out1_pa4` — separate grammars.
51
- const ST_ADC = /^([a-z]*adc\d*_(?:in|inp)\d+_p([a-z])(\d+))$/;
52
- const ST_PWM = /^(tim(\d+)_ch(\d+)_p([a-z])(\d+))$/;
53
- const ST_PWM_F1 = /^(tim(\d+)_ch(\d+)_pwm_out_p([a-z])(\d+))$/;
54
- const ST_DAC = /^(dac(\d+)_out(\d+)_p([a-z])(\d+))$/;
55
- const ST_DAC_F1 = /^(dac_out(\d+)_p([a-z])(\d+))$/;
56
- // i.MX RT labels: iomuxc_<pad>_adc1_in1 / iomuxc_<pad>_flexpwm2_pwma3,
57
- // with the pad→GPIO join as sibling iomuxc_<pad>_gpio1_io12 labels. The NEW
58
- // RT parts (rt11xx/rt116x/rt118x) use `…_flexpwm1_pwm0_a` (pwm<K>_<a|b> →
59
- // A=0/B=1). Their ADC is the LPADC (`lpadc<N>`) and stays out — see
60
- // dts-reader.ts.
61
- const IMX_GPIO = /^iomuxc_([a-z0-9_]+)_gpio(\d+)_io(\d+)$/;
62
- const IMX_ADC = /^iomuxc_([a-z0-9_]+)_adc(\d+)_in(\d+)$/;
63
- const IMX_PWM = /^iomuxc_([a-z0-9_]+)_flexpwm(\d+)_pwm([ab])(\d+)$/;
64
- const IMX_PWM2 = /^iomuxc_([a-z0-9_]+)_flexpwm(\d+)_pwm(\d+)_([ab])$/;
65
-
66
- /**
67
- * Parse a resolved zephyr.dts into raw pin routes. Labels are matched as
68
- * whole tokens (`label:` or `label {`) — in dtc output every node carries
69
- * its labels inline before the node name.
70
- */
71
- export function parseZephyrDts(text: string): AsBuiltFacts {
72
- const facts: AsBuiltFacts = { adc: [], pwm: [], dac: [] };
73
- const imxPadToGpio = new Map<string, { port: string; bit: number }>();
74
- const seen = new Set<string>();
75
- // Every label occurrence: `label:` (label before node) — zephyr.dts
76
- // renders `label: nodename {`, INDENTED with tabs. Whole-token labels.
77
- for (const m of text.matchAll(/^\s*([a-z][a-z0-9_]*):\s/gm)) {
78
- const label = m[1]!;
79
- if (seen.has(label)) continue;
80
- let g: RegExpMatchArray | null;
81
- if ((g = label.match(ST_ADC))) {
82
- seen.add(label);
83
- // Source: the unit prefix from the name — `adc1_in1…` → adc1, the
84
- // single-unit spelling `adc_in0…` → adc.
85
- const unit = label.match(/^([a-z]+?\d*)_in/);
86
- facts.adc.push({
87
- source: unit ? unit[1]! : 'adc',
88
- channel: Number(label.match(/_(?:in|inp)(\d+)_/)![1]),
89
- port: g[2]!.toUpperCase(),
90
- bit: Number(g[3]),
91
- pinctrl: label,
92
- });
93
- } else if ((g = label.match(ST_PWM))) {
94
- seen.add(label);
95
- facts.pwm.push({
96
- source: `tim${g[2]}`,
97
- channel: Number(g[3]),
98
- port: g[4]!.toUpperCase(),
99
- bit: Number(g[5]),
100
- pinctrl: label,
101
- });
102
- } else if ((g = label.match(ST_PWM_F1))) {
103
- seen.add(label);
104
- facts.pwm.push({
105
- source: `tim${g[2]}`,
106
- channel: Number(g[3]),
107
- port: g[4]!.toUpperCase(),
108
- bit: Number(g[5]),
109
- pinctrl: label,
110
- });
111
- } else if ((g = label.match(ST_DAC))) {
112
- seen.add(label);
113
- facts.dac.push({
114
- source: `dac${g[2]}`,
115
- channel: Number(g[3]),
116
- port: g[4]!.toUpperCase(),
117
- bit: Number(g[5]),
118
- pinctrl: label,
119
- });
120
- } else if ((g = label.match(ST_DAC_F1))) {
121
- seen.add(label);
122
- facts.dac.push({
123
- source: 'dac1',
124
- channel: Number(g[2]),
125
- port: g[3]!.toUpperCase(),
126
- bit: Number(g[4]),
127
- pinctrl: label,
128
- });
129
- } else if ((g = label.match(IMX_GPIO))) {
130
- seen.add(label);
131
- if (!imxPadToGpio.has(g[1]!)) imxPadToGpio.set(g[1]!, { port: g[2]!, bit: Number(g[3]) });
132
- }
133
- }
134
- // i.MX routes resolve after the join map is complete.
135
- for (const m of text.matchAll(/^\s*([a-z][a-z0-9_]*):\s/gm)) {
136
- const label = m[1]!;
137
- let g: RegExpMatchArray | null;
138
- if ((g = label.match(IMX_ADC))) {
139
- const gpio = imxPadToGpio.get(g[1]!);
140
- if (!gpio || seen.has(label)) continue;
141
- seen.add(label);
142
- facts.adc.push({
143
- source: `adc${g[2]}`,
144
- channel: Number(g[3]),
145
- port: gpio.port,
146
- bit: gpio.bit,
147
- pinctrl: label,
148
- });
149
- } else if ((g = label.match(IMX_PWM))) {
150
- const gpio = imxPadToGpio.get(g[1]!);
151
- if (!gpio || seen.has(label)) continue;
152
- seen.add(label);
153
- facts.pwm.push({
154
- source: `flexpwm${g[2]}_pwm${g[4]}`,
155
- channel: g[3] === 'b' ? 1 : 0,
156
- port: gpio.port,
157
- bit: gpio.bit,
158
- pinctrl: label,
159
- });
160
- } else if ((g = label.match(IMX_PWM2))) {
161
- const gpio = imxPadToGpio.get(g[1]!);
162
- if (!gpio || seen.has(label)) continue;
163
- seen.add(label);
164
- facts.pwm.push({
165
- source: `flexpwm${g[2]}_pwm${g[3]}`,
166
- channel: g[4] === 'b' ? 1 : 0,
167
- port: gpio.port,
168
- bit: gpio.bit,
169
- pinctrl: label,
170
- });
171
- }
172
- }
173
- return facts;
174
- }
175
-
176
- /** Parse + shape-validate .cuttlefish/as-built.json; errors name the file. */
177
- export function parseAsBuiltJson(text: string, source = '.cuttlefish/as-built.json'): AsBuiltFile {
178
- let parsed: unknown;
179
- try {
180
- parsed = JSON.parse(text);
181
- } catch (err) {
182
- throw new Error(`${source} is not valid JSON: ${(err as Error).message}`);
183
- }
184
- const file = parsed as AsBuiltFile;
185
- if (!file || typeof file !== 'object' || file.version !== 1 || typeof file.board !== 'string'
186
- || !file.routes || !Array.isArray(file.routes.adcPins) || !Array.isArray(file.routes.pwmPins)) {
187
- throw new Error(`${source} must carry version 1, a board target, and routes.{adcPins,pwmPins}.`);
188
- }
189
- return file;
190
- }
191
-
192
- /** Serialize the as-built snapshot (deterministic field order). */
193
- export function asBuiltJson(board: string, facts: AsBuiltFacts): string {
194
- const byPin = (a: AsBuiltRoute, b: AsBuiltRoute) =>
195
- a.port.localeCompare(b.port) || a.bit - b.bit || a.channel - b.channel;
196
- return JSON.stringify({
197
- version: 1,
198
- board,
199
- generatedAt: new Date().toISOString(),
200
- routes: {
201
- adcPins: [...facts.adc].sort(byPin),
202
- pwmPins: [...facts.pwm].sort(byPin),
203
- dacPins: [...facts.dac].sort(byPin),
204
- },
205
- }, null, 1);
206
- }
1
+ // ---------------------------------------------------------------------------
2
+ // as-built.ts — harvest pin routes from a build's resolved zephyr.dts
3
+ //
4
+ // The third fact source: after any successful `west build`, the fully
5
+ // merged devicetree at <build>/zephyr/zephyr.dts contains the board's
6
+ // pinctrl nodes with LABELS verbatim — the stable name grammar
7
+ // (`adc1_in1_pa1`, `tim4_ch1_pb6`, `iomuxc_<pad>_adc1_in1`) — with the
8
+ // vendor macro values already expanded away. Parsing names only needs ONE
9
+ // grammar per shape, immune to vendor macro churn, and corrects a broken
10
+ // catalog harvest per-pin before the next build (the walker's macro-value
11
+ // grammars are the fragile half; this is the robust half).
12
+ //
13
+ // Scope is honest: only node-name families appear (STM32-style dtsi nodes
14
+ // and the i.MX RT label grammar with its in-band pad join). Header-matrix
15
+ // families (Kinetis/LPC/GD32 macro routing, ESP32/RP2 matrices) never
16
+ // exist as DT nodes — the walker + family tables remain their source.
17
+ // ----------------------------------------------------------------------------
18
+
19
+ export interface AsBuiltRoute {
20
+ source: string;
21
+ channel: number;
22
+ port: string;
23
+ bit: number;
24
+ pinctrl: string;
25
+ }
26
+
27
+ export interface AsBuiltFacts {
28
+ adc: AsBuiltRoute[];
29
+ pwm: AsBuiltRoute[];
30
+ dac: AsBuiltRoute[];
31
+ }
32
+
33
+ /** The .typecad-hal/as-built.json shape (machine-authored facts file). */
34
+ export interface AsBuiltFile {
35
+ version: 1;
36
+ board: string;
37
+ generatedAt: string;
38
+ routes: {
39
+ adcPins: AsBuiltRoute[];
40
+ pwmPins: AsBuiltRoute[];
41
+ dacPins: AsBuiltRoute[];
42
+ };
43
+ }
44
+
45
+ // STM32-style pinctrl dtsi labels (all spellings the walker knows):
46
+ // adc1_in1_pa1 / adc_in0_pa0 / adc1_inp16_pa0, tim4_ch1_pb6,
47
+ // dac1_out1_pa4. Labels appear verbatim in zephyr.dts
48
+ // (`adc1_in0_pa0: adc1_in0_pa0 {`). The `inn` negative inputs stay out —
49
+ // the thin HAL is single-ended. The F1 (AFIO) family uses `tim1_ch1_pwm_out_pa8`
50
+ // and `dac_out1_pa4` — separate grammars.
51
+ const ST_ADC = /^([a-z]*adc\d*_(?:in|inp)\d+_p([a-z])(\d+))$/;
52
+ const ST_PWM = /^(tim(\d+)_ch(\d+)_p([a-z])(\d+))$/;
53
+ const ST_PWM_F1 = /^(tim(\d+)_ch(\d+)_pwm_out_p([a-z])(\d+))$/;
54
+ const ST_DAC = /^(dac(\d+)_out(\d+)_p([a-z])(\d+))$/;
55
+ const ST_DAC_F1 = /^(dac_out(\d+)_p([a-z])(\d+))$/;
56
+ // i.MX RT labels: iomuxc_<pad>_adc1_in1 / iomuxc_<pad>_flexpwm2_pwma3,
57
+ // with the pad→GPIO join as sibling iomuxc_<pad>_gpio1_io12 labels. The NEW
58
+ // RT parts (rt11xx/rt116x/rt118x) use `…_flexpwm1_pwm0_a` (pwm<K>_<a|b> →
59
+ // A=0/B=1). Their ADC is the LPADC (`lpadc<N>`) and stays out — see
60
+ // dts-reader.ts.
61
+ const IMX_GPIO = /^iomuxc_([a-z0-9_]+)_gpio(\d+)_io(\d+)$/;
62
+ const IMX_ADC = /^iomuxc_([a-z0-9_]+)_adc(\d+)_in(\d+)$/;
63
+ const IMX_PWM = /^iomuxc_([a-z0-9_]+)_flexpwm(\d+)_pwm([ab])(\d+)$/;
64
+ const IMX_PWM2 = /^iomuxc_([a-z0-9_]+)_flexpwm(\d+)_pwm(\d+)_([ab])$/;
65
+
66
+ /**
67
+ * Parse a resolved zephyr.dts into raw pin routes. Labels are matched as
68
+ * whole tokens (`label:` or `label {`) — in dtc output every node carries
69
+ * its labels inline before the node name.
70
+ */
71
+ export function parseZephyrDts(text: string): AsBuiltFacts {
72
+ const facts: AsBuiltFacts = { adc: [], pwm: [], dac: [] };
73
+ const imxPadToGpio = new Map<string, { port: string; bit: number }>();
74
+ const seen = new Set<string>();
75
+ // Every label occurrence: `label:` (label before node) — zephyr.dts
76
+ // renders `label: nodename {`, INDENTED with tabs. Whole-token labels.
77
+ for (const m of text.matchAll(/^\s*([a-z][a-z0-9_]*):\s/gm)) {
78
+ const label = m[1]!;
79
+ if (seen.has(label)) continue;
80
+ let g: RegExpMatchArray | null;
81
+ if ((g = label.match(ST_ADC))) {
82
+ seen.add(label);
83
+ // Source: the unit prefix from the name — `adc1_in1…` → adc1, the
84
+ // single-unit spelling `adc_in0…` → adc.
85
+ const unit = label.match(/^([a-z]+?\d*)_in/);
86
+ facts.adc.push({
87
+ source: unit ? unit[1]! : 'adc',
88
+ channel: Number(label.match(/_(?:in|inp)(\d+)_/)![1]),
89
+ port: g[2]!.toUpperCase(),
90
+ bit: Number(g[3]),
91
+ pinctrl: label,
92
+ });
93
+ } else if ((g = label.match(ST_PWM))) {
94
+ seen.add(label);
95
+ facts.pwm.push({
96
+ source: `tim${g[2]}`,
97
+ channel: Number(g[3]),
98
+ port: g[4]!.toUpperCase(),
99
+ bit: Number(g[5]),
100
+ pinctrl: label,
101
+ });
102
+ } else if ((g = label.match(ST_PWM_F1))) {
103
+ seen.add(label);
104
+ facts.pwm.push({
105
+ source: `tim${g[2]}`,
106
+ channel: Number(g[3]),
107
+ port: g[4]!.toUpperCase(),
108
+ bit: Number(g[5]),
109
+ pinctrl: label,
110
+ });
111
+ } else if ((g = label.match(ST_DAC))) {
112
+ seen.add(label);
113
+ facts.dac.push({
114
+ source: `dac${g[2]}`,
115
+ channel: Number(g[3]),
116
+ port: g[4]!.toUpperCase(),
117
+ bit: Number(g[5]),
118
+ pinctrl: label,
119
+ });
120
+ } else if ((g = label.match(ST_DAC_F1))) {
121
+ seen.add(label);
122
+ facts.dac.push({
123
+ source: 'dac1',
124
+ channel: Number(g[2]),
125
+ port: g[3]!.toUpperCase(),
126
+ bit: Number(g[4]),
127
+ pinctrl: label,
128
+ });
129
+ } else if ((g = label.match(IMX_GPIO))) {
130
+ seen.add(label);
131
+ if (!imxPadToGpio.has(g[1]!)) imxPadToGpio.set(g[1]!, { port: g[2]!, bit: Number(g[3]) });
132
+ }
133
+ }
134
+ // i.MX routes resolve after the join map is complete.
135
+ for (const m of text.matchAll(/^\s*([a-z][a-z0-9_]*):\s/gm)) {
136
+ const label = m[1]!;
137
+ let g: RegExpMatchArray | null;
138
+ if ((g = label.match(IMX_ADC))) {
139
+ const gpio = imxPadToGpio.get(g[1]!);
140
+ if (!gpio || seen.has(label)) continue;
141
+ seen.add(label);
142
+ facts.adc.push({
143
+ source: `adc${g[2]}`,
144
+ channel: Number(g[3]),
145
+ port: gpio.port,
146
+ bit: gpio.bit,
147
+ pinctrl: label,
148
+ });
149
+ } else if ((g = label.match(IMX_PWM))) {
150
+ const gpio = imxPadToGpio.get(g[1]!);
151
+ if (!gpio || seen.has(label)) continue;
152
+ seen.add(label);
153
+ facts.pwm.push({
154
+ source: `flexpwm${g[2]}_pwm${g[4]}`,
155
+ channel: g[3] === 'b' ? 1 : 0,
156
+ port: gpio.port,
157
+ bit: gpio.bit,
158
+ pinctrl: label,
159
+ });
160
+ } else if ((g = label.match(IMX_PWM2))) {
161
+ const gpio = imxPadToGpio.get(g[1]!);
162
+ if (!gpio || seen.has(label)) continue;
163
+ seen.add(label);
164
+ facts.pwm.push({
165
+ source: `flexpwm${g[2]}_pwm${g[3]}`,
166
+ channel: g[4] === 'b' ? 1 : 0,
167
+ port: gpio.port,
168
+ bit: gpio.bit,
169
+ pinctrl: label,
170
+ });
171
+ }
172
+ }
173
+ return facts;
174
+ }
175
+
176
+ /** Parse + shape-validate .typecad-hal/as-built.json; errors name the file. */
177
+ export function parseAsBuiltJson(text: string, source = '.typecad-hal/as-built.json'): AsBuiltFile {
178
+ let parsed: unknown;
179
+ try {
180
+ parsed = JSON.parse(text);
181
+ } catch (err) {
182
+ throw new Error(`${source} is not valid JSON: ${(err as Error).message}`);
183
+ }
184
+ const file = parsed as AsBuiltFile;
185
+ if (!file || typeof file !== 'object' || file.version !== 1 || typeof file.board !== 'string'
186
+ || !file.routes || !Array.isArray(file.routes.adcPins) || !Array.isArray(file.routes.pwmPins)) {
187
+ throw new Error(`${source} must carry version 1, a board target, and routes.{adcPins,pwmPins}.`);
188
+ }
189
+ return file;
190
+ }
191
+
192
+ /** Serialize the as-built snapshot (deterministic field order). */
193
+ export function asBuiltJson(board: string, facts: AsBuiltFacts): string {
194
+ const byPin = (a: AsBuiltRoute, b: AsBuiltRoute) =>
195
+ a.port.localeCompare(b.port) || a.bit - b.bit || a.channel - b.channel;
196
+ return JSON.stringify({
197
+ version: 1,
198
+ board,
199
+ generatedAt: new Date().toISOString(),
200
+ routes: {
201
+ adcPins: [...facts.adc].sort(byPin),
202
+ pwmPins: [...facts.pwm].sort(byPin),
203
+ dacPins: [...facts.dac].sort(byPin),
204
+ },
205
+ }, null, 1);
206
+ }