@typecad/framework-zephyr 1.0.0-alpha.18 → 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 (114) hide show
  1. package/dist/audit.d.ts +111 -0
  2. package/dist/audit.js +416 -0
  3. package/dist/boardgen.js +63 -5
  4. package/dist/chips/resolve.js +20 -0
  5. package/dist/chips/types.d.ts +21 -0
  6. package/dist/display/bindings.d.ts +55 -0
  7. package/dist/display/bindings.js +316 -0
  8. package/dist/display/gfx.d.ts +2 -3
  9. package/dist/display/gfx.js +166 -154
  10. package/dist/display/index.js +20 -1
  11. package/dist/display/mipi-dbi-host.d.ts +9 -0
  12. package/dist/display/mipi-dbi-host.js +174 -0
  13. package/dist/display/profiles.d.ts +109 -4
  14. package/dist/display/profiles.js +270 -7
  15. package/dist/display/touch-adapter.js +118 -48
  16. package/dist/display/ui-adapter-eink.d.ts +2 -0
  17. package/dist/display/ui-adapter-eink.js +4 -0
  18. package/dist/display/ui-adapter-gray.d.ts +8 -0
  19. package/dist/display/ui-adapter-gray.js +170 -0
  20. package/dist/display/ui-adapter-mono.d.ts +13 -0
  21. package/dist/display/ui-adapter-mono.js +230 -0
  22. package/dist/display/ui-adapter-native.d.ts +10 -0
  23. package/dist/display/ui-adapter-native.js +295 -0
  24. package/dist/display/ui-adapter-shared.d.ts +11 -0
  25. package/dist/display/ui-adapter-shared.js +122 -0
  26. package/dist/display/ui-adapter.js +53 -101
  27. package/dist/dt-config/kconfig.d.ts +62 -1
  28. package/dist/dt-config/kconfig.js +123 -26
  29. package/dist/dt-config/overlay.d.ts +13 -0
  30. package/dist/dt-config/overlay.js +431 -16
  31. package/dist/framework.manifest.d.ts +9 -3
  32. package/dist/framework.manifest.js +123 -10
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +9 -1
  35. package/dist/licenses.js +5 -84
  36. package/dist/lowering/can.d.ts +25 -0
  37. package/dist/lowering/can.js +97 -0
  38. package/dist/lowering/clock.d.ts +17 -0
  39. package/dist/lowering/clock.js +58 -0
  40. package/dist/lowering/hid.d.ts +27 -0
  41. package/dist/lowering/hid.js +244 -0
  42. package/dist/lowering/i2c.d.ts +8 -0
  43. package/dist/lowering/i2c.js +140 -0
  44. package/dist/lowering/i2s.d.ts +27 -0
  45. package/dist/lowering/i2s.js +98 -0
  46. package/dist/lowering/index.d.ts +9 -1
  47. package/dist/lowering/index.js +25 -1
  48. package/dist/lowering/matrix.d.ts +15 -0
  49. package/dist/lowering/matrix.js +63 -0
  50. package/dist/lowering/power.d.ts +10 -0
  51. package/dist/lowering/power.js +35 -0
  52. package/dist/lowering/pwm.js +25 -0
  53. package/dist/lowering/sensor.d.ts +2 -2
  54. package/dist/lowering/sensor.js +8 -4
  55. package/dist/lowering/strip.d.ts +16 -0
  56. package/dist/lowering/strip.js +70 -0
  57. package/dist/lowering/thread.js +5 -1
  58. package/dist/lowering/trace.d.ts +44 -0
  59. package/dist/lowering/trace.js +239 -0
  60. package/dist/lowering/uart.js +6 -1
  61. package/dist/lowering/usb.d.ts +3 -1
  62. package/dist/lowering/usb.js +6 -3
  63. package/dist/sbom.d.ts +181 -0
  64. package/dist/sbom.js +901 -0
  65. package/dist/strategy.d.ts +60 -2
  66. package/dist/strategy.js +485 -25
  67. package/dist/toolchain/index.d.ts +12 -1
  68. package/dist/toolchain/index.js +115 -27
  69. package/dist/toolchain/scaffold.d.ts +4 -1
  70. package/dist/toolchain/scaffold.js +67 -3
  71. package/dist/west-inventory.d.ts +25 -0
  72. package/dist/west-inventory.js +97 -0
  73. package/package.json +3 -3
  74. package/src/audit.ts +529 -0
  75. package/src/boardgen.ts +54 -5
  76. package/src/chips/resolve.ts +21 -0
  77. package/src/chips/types.ts +576 -567
  78. package/src/display/bindings.ts +347 -0
  79. package/src/display/gfx.ts +318 -306
  80. package/src/display/index.ts +87 -70
  81. package/src/display/mipi-dbi-host.ts +183 -0
  82. package/src/display/profiles.ts +458 -139
  83. package/src/display/touch-adapter.ts +274 -204
  84. package/src/display/ui-adapter-eink.ts +13 -0
  85. package/src/display/ui-adapter-gray.ts +178 -0
  86. package/src/display/ui-adapter-mono.ts +238 -0
  87. package/src/display/ui-adapter-native.ts +304 -0
  88. package/src/display/ui-adapter-shared.ts +125 -0
  89. package/src/display/ui-adapter.ts +51 -100
  90. package/src/dt-config/kconfig.ts +647 -511
  91. package/src/dt-config/overlay.ts +433 -16
  92. package/src/framework.manifest.ts +131 -10
  93. package/src/index.ts +11 -1
  94. package/src/licenses.ts +5 -84
  95. package/src/lowering/can.ts +140 -0
  96. package/src/lowering/clock.ts +91 -0
  97. package/src/lowering/hid.ts +261 -0
  98. package/src/lowering/i2c.ts +146 -0
  99. package/src/lowering/i2s.ts +143 -0
  100. package/src/lowering/index.ts +18 -1
  101. package/src/lowering/matrix.ts +70 -0
  102. package/src/lowering/power.ts +41 -0
  103. package/src/lowering/pwm.ts +192 -167
  104. package/src/lowering/sensor.ts +159 -155
  105. package/src/lowering/strip.ts +81 -0
  106. package/src/lowering/thread.ts +5 -1
  107. package/src/lowering/trace.ts +270 -0
  108. package/src/lowering/uart.ts +6 -1
  109. package/src/lowering/usb.ts +224 -221
  110. package/src/sbom.ts +1117 -0
  111. package/src/strategy.ts +440 -25
  112. package/src/toolchain/index.ts +117 -28
  113. package/src/toolchain/scaffold.ts +62 -3
  114. package/src/west-inventory.ts +102 -0
@@ -24,6 +24,7 @@ import { readdirSync, readFileSync, mkdirSync, rmSync, existsSync, writeFileSync
24
24
  import { parseCompileErrors } from '@typecad/cuttlefish/api/shared';
25
25
  import { scaffoldZephyrProject, writeIfChanged, appendLibraryOverlayFragments } from './scaffold.js';
26
26
  import { parseZephyrDts, asBuiltJson } from '../as-built.js';
27
+ import { stampBuildSbom } from '../sbom.js';
27
28
  import { westSpawn, buildEnv } from './west-spawn.js';
28
29
  import { discoverWest } from './west-discover.js';
29
30
  import { writeDebugConfig, resolveDebugLocations, debugArtifactsNeedRewrite, DEBUG_SERVER_PORT, DEBUG_SERVER_TCL_PORT } from './debug-config.js';
@@ -36,7 +37,7 @@ import { NO_BOARD_CHIP } from '../chips/index.js';
36
37
  import { resolveChipFromBoard } from '../chips/resolve.js';
37
38
  import { pwmDtAliasToken } from '../lowering/pwm.js';
38
39
  import { detectZephyrVersion, checkZephyrCompat, resolveBoardTarget } from './compat.js';
39
- import { DEFAULT_ZEPHYR_DISPLAY_PROFILE } from '../display/profiles.js';
40
+ import { DEFAULT_ZEPHYR_DISPLAY_PROFILE, profileFromEmittedSource } from '../display/profiles.js';
40
41
  /**
41
42
  * Resolve the chip for a build the same way the strategy does at emit time —
42
43
  * from the board constants the transpile persisted next to the emitted
@@ -196,6 +197,36 @@ function scanPwmUsedPins(src, chip) {
196
197
  }
197
198
  return pins;
198
199
  }
200
+ export function scanMatrix(src) {
201
+ // The on_key lowering's comment marker carries the construction lists.
202
+ const m = src.match(/cuttlefish-matrix: rows=([\d,]*) cols=([\d,]*)/);
203
+ if (!m)
204
+ return undefined;
205
+ const rows = m[1] ? m[1].split(',').filter(Boolean).map(Number) : [];
206
+ const cols = m[2] ? m[2].split(',').filter(Boolean).map(Number) : [];
207
+ if (rows.length === 0 || cols.length === 0)
208
+ return undefined;
209
+ return { rows, cols };
210
+ }
211
+ export function scanHid(src) {
212
+ // The shim's report buffer names are the presence signal.
213
+ if (src.includes('__tc_mouse_report'))
214
+ return 'mouse';
215
+ if (src.includes('__tc_kb_report'))
216
+ return 'keyboard';
217
+ return undefined;
218
+ }
219
+ export function scanStrips(src) {
220
+ // The shim's buffer declaration is the presence signal:
221
+ // `static struct led_rgb __tc_strip<N>_buf[<COUNT>];`
222
+ const out = new Map();
223
+ for (const m of src.matchAll(/__tc_strip(\d+)_buf\[(\d+)\]/g)) {
224
+ const idx = Number(m[1]);
225
+ const count = Number(m[2]);
226
+ out.set(idx, Math.max(out.get(idx) ?? 0, count));
227
+ }
228
+ return [...out.entries()].map(([index, count]) => ({ index, count }));
229
+ }
199
230
  /**
200
231
  * Bus controller indexes the emitted sources actually reference — the shim
201
232
  * declares one `__tc_<bus><N>_dev` state block per used instance (gated by
@@ -230,13 +261,23 @@ export function scanSpiTargets(src) {
230
261
  }
231
262
  export function scanSensorParts(src) {
232
263
  const out = new Map();
264
+ // 1-Wire sensors: the stem carries the data pin (w1 master's GPIO).
265
+ for (const m of src.matchAll(/__tc_sensor_([a-z0-9_]+)_w1_p(\d+)_dev\b/g)) {
266
+ out.set(`${m[1]}|w1|${m[2]}`, { part: m[1], busIndex: 0, port: parseInt(m[2], 10), busKind: 'w1', spiHz: 0, spiMode: 0, alertPin: -1, resolution: 12 });
267
+ }
233
268
  for (const m of src.matchAll(/__tc_sensor_([a-z0-9_]+)_(i2c|spi)(\d+)_(0x[0-9a-f]+|cs[0-9]+)_dev\b/g)) {
234
269
  const port = m[4].startsWith('0x') ? parseInt(m[4], 16) : parseInt(m[4].slice(2), 10);
235
- const ref = { part: m[1], busIndex: parseInt(m[3], 10), port, busKind: m[2], spiHz: 0, spiMode: 0, alertPin: -1 };
270
+ const ref = { part: m[1], busIndex: parseInt(m[3], 10), port, busKind: m[2], spiHz: 0, spiMode: 0, alertPin: -1, resolution: 12 };
236
271
  out.set(`${ref.part}|${ref.busKind}${ref.busIndex}|${ref.port}`, ref);
237
272
  }
238
273
  // Construction facts ride the state block's config comment — merge by
239
274
  // nodelabel so the scanner stays the single source for the overlay.
275
+ for (const m of src.matchAll(/tc-sensor-cfg: tc_([a-z0-9_]+)_w1_p(\d+) res=(\d+)/g)) {
276
+ const key = `${m[1]}|w1|${m[2]}`;
277
+ const existing = out.get(key);
278
+ if (existing)
279
+ existing.resolution = parseInt(m[3], 10);
280
+ }
240
281
  for (const m of src.matchAll(/tc-sensor-cfg: tc_([a-z0-9_]+)_(i2c|spi)(\d+)_(0x[0-9a-f]+|cs[0-9]+) hz=(\d+) mode=(\d+) alert=(-?\d+)/g)) {
241
282
  const port = m[4].startsWith('0x') ? parseInt(m[4], 16) : parseInt(m[4].slice(2), 10);
242
283
  const key = `${m[1]}|${m[2]}${m[3]}|${port}`;
@@ -631,11 +672,13 @@ export const Toolchain = {
631
672
  const usesDisplay = uses('display_write') || uses('display_init')
632
673
  || uses('display_fill_rect') || uses('__tc_display_dev')
633
674
  || uses('CuttlefishDisplayTarget');
634
- // Both registered Zephyr display profiles use dtLabel 'display0', so the
635
- // default profile's overlay block (&display0 { status="okay" }) is correct
636
- // for either driver. Thread a non-default profile here only if a future
637
- // board carries a display node under a different nodelabel.
638
- const displayProfile = usesDisplay ? DEFAULT_ZEPHYR_DISPLAY_PROFILE : undefined;
675
+ // Recover the exact display profile from the marker the adapter stamped
676
+ // into the emitted source (the registry is the single source of truth).
677
+ // Fall back to the default profile only when a display is used but no
678
+ // marker matched (e.g. hand-written source).
679
+ const displayProfile = usesDisplay
680
+ ? (profileFromEmittedSource(src) ?? DEFAULT_ZEPHYR_DISPLAY_PROFILE)
681
+ : undefined;
639
682
  // Touch controller kind comes from which DT nodelabel the emitted adapter
640
683
  // references (FT6336U on I2C, XPT2046 on the display's SPI bus).
641
684
  const usesTouch = uses('ft6336u') || uses('touch_');
@@ -654,12 +697,29 @@ export const Toolchain = {
654
697
  spiTargets: spiTargetParts,
655
698
  usesSpi: uses('spi_') || uses('__tc_spi') || spiTargetParts.length > 0,
656
699
  usesUart: uses('__tc_uart'),
657
- usesUsb: uses('__tc_usb'),
700
+ usesUsb: uses('__tc_usb0'),
658
701
  usesPwm: uses('pwm_'),
659
702
  usesAdc: uses('adc_'),
660
703
  adcReadPins: scanAdcReadPins(src, chip),
661
704
  dacWritePins: scanDacWritePins(src),
662
705
  pwmUsedPins: scanPwmUsedPins(src, chip),
706
+ usesStrip: uses('led_strip'),
707
+ strips: scanStrips(src),
708
+ usesClock: uses('__tc_rtc'),
709
+ usesCan: uses('can_'),
710
+ canLoopback: uses('CAN_MODE_LOOPBACK'),
711
+ usesI2s: uses('i2s_'),
712
+ clockShimCounter: chip.hwtimer && chip.hwtimer.controllers.length > 0
713
+ ? {
714
+ label: chip.hwtimer.controllers[0].nodeLabel,
715
+ ...(chip.hwtimer.controllers[0].counterParent ? { parent: chip.hwtimer.controllers[0].counterParent } : {}),
716
+ }
717
+ : undefined,
718
+ usesHid: uses('__tc_hid_'),
719
+ hidProtocol: scanHid(src),
720
+ usesMatrix: uses('__tc_matrix'),
721
+ matrix: scanMatrix(src),
722
+ usesPower: uses('sys_poweroff'),
663
723
  i2cUsedInstances: scanUsedBusInstances(src, chip.i2c?.controllers, 'i2c'),
664
724
  spiUsedInstances: scanUsedBusInstances(src, chip.spi?.controllers, 'spi'),
665
725
  uartUsedInstances: scanUsedBusInstances(src, chip.uart?.controllers, 'uart'),
@@ -723,7 +783,8 @@ export const Toolchain = {
723
783
  const isGdbDebug = o.debug === true && debugMode === 'gdb';
724
784
  const zc = o.zephyrConfig;
725
785
  const userKconfig = zc?.kconfig;
726
- const configChanged = scaffoldZephyrProject(projectRoot, isGdbDebug, userKconfig, o.psram);
786
+ const traceCfg = zc?.trace;
787
+ const configChanged = scaffoldZephyrProject(projectRoot, isGdbDebug, userKconfig, o.psram, traceCfg);
727
788
  // Regenerate the DT overlay for the ACTUAL target board. prepare() writes
728
789
  // it for the default board (the real target is unknown until compile), so
729
790
  // the <default>.overlay it wrote does not match `west build -b <board>`.
@@ -757,28 +818,22 @@ export const Toolchain = {
757
818
  const usesDisplay = uses('display_write') || uses('display_init')
758
819
  || uses('display_fill_rect') || uses('__tc_display_dev')
759
820
  || uses('CuttlefishDisplayTarget');
760
- // Derive the display dimensions from the emitted adapter code
761
- // (display_width/height return the profile's w/h). This ensures the DT
762
- // overlay's width/height match the panel the adapter targets, not the
763
- // default profile — critical for drivers like ST7796S that initialize
764
- // the panel geometry from the DT node.
765
- let displayProfile = usesDisplay ? DEFAULT_ZEPHYR_DISPLAY_PROFILE : undefined;
766
- if (usesDisplay) {
767
- const wMatch = src.match(/display_width\(\)\s*\{\s*return\s+(\d+)\s*;\s*\}/);
768
- const hMatch = src.match(/display_height\(\)\s*\{\s*return\s+(\d+)\s*;\s*\}/);
769
- if (wMatch && hMatch) {
770
- displayProfile = {
771
- ...DEFAULT_ZEPHYR_DISPLAY_PROFILE,
772
- width: parseInt(wMatch[1], 10),
773
- height: parseInt(hMatch[1], 10),
774
- };
775
- }
776
- }
821
+ // Recover the exact display profile from the marker the adapter emitted
822
+ // (`typecad-display-profile: <driver>`), so the DT overlay's compatible
823
+ // string, geometry, and rotation match the panel the adapter targets.
824
+ // The width/height regex this replaces could produce franken-profiles —
825
+ // e.g. an ST7796S build (480x320) on the default ILI9341 profile's
826
+ // controller/compatible, which emitted an ilitek,ili9341 DT node for a
827
+ // panel the ST7796S adapter drives.
828
+ const displayProfile = usesDisplay
829
+ ? (profileFromEmittedSource(src) ?? DEFAULT_ZEPHYR_DISPLAY_PROFILE)
830
+ : undefined;
777
831
  // Extract display pin wiring (cs/dc/rst/spiFrequency/spiPins) from the
778
832
  // config display section so the DT overlay wires the MIPI DBI bridge to
779
833
  // the correct GPIOs + SPI bus pins.
780
834
  const dispCfg = o.display;
781
835
  const spiPins = (dispCfg?.spiPins ?? undefined);
836
+ const i2cPins = (dispCfg?.i2cPins ?? undefined);
782
837
  const wiring = dispCfg
783
838
  ? {
784
839
  cs: typeof dispCfg.cs === 'number' ? dispCfg.cs : undefined,
@@ -788,8 +843,12 @@ export const Toolchain = {
788
843
  sck: typeof spiPins?.sck === 'number' ? spiPins.sck : undefined,
789
844
  mosi: typeof spiPins?.mosi === 'number' ? spiPins.mosi : undefined,
790
845
  miso: typeof spiPins?.miso === 'number' ? spiPins.miso : undefined,
846
+ sda: typeof i2cPins?.sda === 'number' ? i2cPins.sda : undefined,
847
+ scl: typeof i2cPins?.scl === 'number' ? i2cPins.scl : undefined,
791
848
  backlightPin: typeof dispCfg.backlightPin === 'number' ? dispCfg.backlightPin : undefined,
792
849
  tearingEffectPin: typeof dispCfg.tearingEffectPin === 'number' ? dispCfg.tearingEffectPin : undefined,
850
+ busyPin: typeof dispCfg.busyPin === 'number' ? dispCfg.busyPin : undefined,
851
+ address: typeof dispCfg.address === 'number' ? dispCfg.address : undefined,
793
852
  }
794
853
  : undefined;
795
854
  // Extract touch pin wiring from the config display.touch section so the
@@ -841,13 +900,30 @@ export const Toolchain = {
841
900
  spiTargets: spiTargetParts,
842
901
  usesSpi: uses('spi_') || uses('__tc_spi') || spiTargetParts.length > 0,
843
902
  usesUart: uses('__tc_uart'),
844
- usesUsb: uses('__tc_usb'),
903
+ usesUsb: uses('__tc_usb0'),
845
904
  usesPwm: uses('pwm_'),
846
905
  usesAdc: uses('adc_'),
847
906
  adcReadPins: scanAdcReadPins(src, chip),
848
907
  usesDac: uses('dac_') || uses('__tc_dac'),
849
908
  dacWritePins: scanDacWritePins(src),
850
909
  pwmUsedPins: scanPwmUsedPins(src, chip),
910
+ usesStrip: uses('led_strip'),
911
+ strips: scanStrips(src),
912
+ usesHid: uses('__tc_hid_'),
913
+ hidProtocol: scanHid(src),
914
+ usesMatrix: uses('__tc_matrix'),
915
+ matrix: scanMatrix(src),
916
+ usesPower: uses('sys_poweroff'),
917
+ usesClock: uses('__tc_rtc'),
918
+ usesCan: uses('can_'),
919
+ canLoopback: uses('CAN_MODE_LOOPBACK'),
920
+ usesI2s: uses('i2s_'),
921
+ clockShimCounter: chip.hwtimer && chip.hwtimer.controllers.length > 0
922
+ ? {
923
+ label: chip.hwtimer.controllers[0].nodeLabel,
924
+ ...(chip.hwtimer.controllers[0].counterParent ? { parent: chip.hwtimer.controllers[0].counterParent } : {}),
925
+ }
926
+ : undefined,
851
927
  i2cUsedInstances: scanUsedBusInstances(src, chip.i2c?.controllers, 'i2c'),
852
928
  spiUsedInstances: scanUsedBusInstances(src, chip.spi?.controllers, 'spi'),
853
929
  uartUsedInstances: scanUsedBusInstances(src, chip.uart?.controllers, 'uart'),
@@ -1029,6 +1105,18 @@ export const Toolchain = {
1029
1105
  }
1030
1106
  catch { /* best-effort snapshot — nothing to harvest or unreadable */ }
1031
1107
  }
1108
+ // SBOM stamp: after a successful build, record what the binary is made of
1109
+ // — kernel + linked west modules with commit SHAs + the hashed firmware
1110
+ // artifact (CycloneDX 1.6, next to zephyr.bin) — so `typecad-hal sbom
1111
+ // --check` can gate releases on the record still matching the build
1112
+ // (EU CRA essential-requirements support). Best-effort: a west-list or
1113
+ // hashing failure never blocks the build.
1114
+ if (result.status === 0) {
1115
+ try {
1116
+ stampBuildSbom({ buildDir, board, projectRoot });
1117
+ }
1118
+ catch { /* best-effort SBOM stamp */ }
1119
+ }
1032
1120
  return {
1033
1121
  success: result.status === 0,
1034
1122
  output: header + output,
@@ -21,4 +21,7 @@ export declare function appendLibraryOverlayFragments(overlay: string, projectRo
21
21
  *
22
22
  * Idempotent. Mirrors scaffoldEspIdfProject's writeIfChanged discipline.
23
23
  */
24
- export declare function scaffoldZephyrProject(projectRoot: string, debug?: boolean, userKconfig?: Record<string, string>, psram?: 'opi' | 'quad'): boolean;
24
+ export declare function scaffoldZephyrProject(projectRoot: string, debug?: boolean, userKconfig?: Record<string, string>, psram?: 'opi' | 'quad', trace?: {
25
+ enabled?: boolean;
26
+ intervalMs?: number;
27
+ }): boolean;
@@ -9,7 +9,9 @@
9
9
  import { writeFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
11
  import { resolveKconfigFragments } from '../dt-config/kconfig.js';
12
- import { scanSensorParts } from './index.js';
12
+ import { scanSensorParts, scanStrips, scanHid, scanMatrix } from './index.js';
13
+ import { profileFromEmittedSource, transportFor, panelControllerFor } from '../display/profiles.js';
14
+ import { readDisplayBinding } from '../display/bindings.js';
13
15
  import { readCuttlefishLibrarySidecar } from '@typecad/cuttlefish/library-packages';
14
16
  import { SENSOR_PART_INFO } from '@typecad/hal';
15
17
  /** Write a file only if the content differs from the existing file.
@@ -96,7 +98,7 @@ export function appendLibraryOverlayFragments(overlay, projectRoot) {
96
98
  *
97
99
  * Idempotent. Mirrors scaffoldEspIdfProject's writeIfChanged discipline.
98
100
  */
99
- export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, psram) {
101
+ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, psram, trace) {
100
102
  const srcDir = join(projectRoot, 'src');
101
103
  if (!existsSync(srcDir))
102
104
  mkdirSync(srcDir, { recursive: true });
@@ -122,6 +124,22 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
122
124
  const usage = {
123
125
  usesAdc: uses('adc_'),
124
126
  usesPwm: uses('pwm_'),
127
+ usesStrip: uses('led_strip'),
128
+ strips: scanStrips(src),
129
+ // Sensor parts drive CONFIG_W1 (1-Wire masters) and the exception
130
+ // lines; the same scan the exception block below re-runs.
131
+ sensorParts: scanSensorParts(src),
132
+ usesHid: uses('__tc_hid_'),
133
+ hidProtocol: scanHid(src),
134
+ usesMatrix: uses('__tc_matrix'),
135
+ matrix: scanMatrix(src),
136
+ usesPower: uses('sys_poweroff'),
137
+ usesClock: uses('__tc_rtc'),
138
+ usesCan: uses('can_'),
139
+ canLoopback: uses('CAN_MODE_LOOPBACK'),
140
+ usesI2s: uses('i2s_'),
141
+ // No chip facts at scaffold time — the prepare overlay omits the shim;
142
+ // the chip-aware build path synthesizes it.
125
143
  usesDac: uses('dac_') || uses('__tc_dac'),
126
144
  usesFS: uses('__tc_fs'),
127
145
  usesHwtimer: uses('counter_') || uses('__tc_hw'),
@@ -130,6 +148,9 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
130
148
  // call yet), but the shim still declared DEVICE_DT_GET(DT_NODELABEL(...))
131
149
  // so the overlay must enable the node or the device symbol is missing.
132
150
  usesI2c: uses('i2c_') || uses('__tc_i2c'),
151
+ // I2C target mode: every i2c.resp_* lowering calls i2c_target_register
152
+ // (and the shim's state carries the i2c_target_* vocabulary).
153
+ usesI2cTarget: uses('i2c_target_'),
133
154
  // DT-bound sensor parts: the __tc_sensor_* state references and the
134
155
  // sensor_sample_fetch/sensor_channel_get calls both carry the token.
135
156
  usesSensor: uses('sensor_') || uses('__tc_sensor'),
@@ -138,12 +159,32 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
138
159
  usesUart: uses('uart_') || uses('__tc_uart'),
139
160
  // USB CDC serial: every usb.* lowering calls into the __tc_usb<N>_* shim
140
161
  // (device + init helper emitted under usesUsb).
141
- usesUsb: uses('__tc_usb'),
162
+ usesUsb: uses('__tc_usb0'),
142
163
  // STM32F4 DBGMCU keep-SWD-alive init present (emitted for stm32f4 socs).
143
164
  usesStm32DebugSleep: uses('__tc_stm32_dbgmcu'),
144
165
  usesWdt: uses('wdt_'),
145
166
  usesBle: uses('bt_') || uses('bt_gatt') || uses('bt_le_'),
146
167
  usesDisplay: uses('display_write') || uses('display_init') || uses('display_fill_rect') || uses('__tc_display_dev') || uses('CuttlefishDisplayTarget'),
168
+ // Display transport + controller: recovered from the profile marker the
169
+ // display adapters stamp into the emitted source (the profile registry is
170
+ // the single source of truth — no geometry re-derivation here).
171
+ ...(() => {
172
+ const profile = profileFromEmittedSource(src);
173
+ if (!profile)
174
+ return {};
175
+ // Registry profiles name a DT compatible via dtCompatible (the driver
176
+ // id itself isn't one); drop-in profiles use the driver string directly.
177
+ const bus = readDisplayBinding(profile.dtCompatible ?? profile.driver)?.busFamily;
178
+ if (bus === 'i2c')
179
+ return { displayBus: 'i2c' };
180
+ // Board-provided panels (native_sim's sdl_dc) carry their own fragments
181
+ // and no bus at all.
182
+ if (profile.boardProvidesDisplay)
183
+ return { displayKconfigExtra: profile.kconfig };
184
+ if (transportFor(profile) !== 'zephyr-display')
185
+ return {};
186
+ return { displayTransport: 'zephyr-display', displayController: panelControllerFor(profile) };
187
+ })(),
147
188
  usesTouch: uses('ft6336u') || uses('touch_'),
148
189
  // (The power HAL is removed; its former pm_/k_sleep token scan is gone —
149
190
  // nothing here must match tx_power_dbm's `power` substring.)
@@ -266,6 +307,29 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
266
307
  }
267
308
  }
268
309
  }
310
+ // ── Trace heartbeat Kconfig ────────────────────────────────────────────────
311
+ // zephyr.trace.enabled in typecad-hal.config.ts turns on the [TR: sampler
312
+ // (emitted by the strategy at transpile time from the same config record).
313
+ // The symbol set is exactly what the sampler calls: runtime stats
314
+ // (k_thread_runtime_stats_*), the monitor list (k_thread_foreach —
315
+ // kernel/thread_monitor.c only links under THREAD_MONITOR), thread names
316
+ // (k_thread_name_get/set), and stack inspection (0xAA fill +
317
+ // stack_info.size). User zephyr.kconfig overrides still win.
318
+ if (trace?.enabled === true) {
319
+ const traceSymbols = [
320
+ ['CONFIG_THREAD_RUNTIME_STATS', 'y'],
321
+ ['CONFIG_THREAD_MONITOR', 'y'],
322
+ ['CONFIG_THREAD_NAME', 'y'],
323
+ ['CONFIG_INIT_STACKS', 'y'],
324
+ ['CONFIG_THREAD_STACK_INFO', 'y'],
325
+ ];
326
+ prjConf.push('', '# Trace heartbeat (typecad-hal.config.ts → zephyr.trace).');
327
+ for (const [sym, val] of traceSymbols) {
328
+ if (userKconfig && userKconfig.hasOwnProperty(sym))
329
+ continue;
330
+ prjConf.push(`${sym}=${val}`);
331
+ }
332
+ }
269
333
  // Per-part Kconfig exceptions: catalog parts whose driver is NOT default-y
270
334
  // on its DT node carry extra lines here (empty today — every in-tree sensor
271
335
  // driver lights up from the node; the path exists so the first exception
@@ -0,0 +1,25 @@
1
+ import type { ReadDir, ReadFile } from '@typecad/cuttlefish/api/shared';
2
+ /** Normalize a path for case-insensitive, separator-agnostic comparison. */
3
+ export declare function norm(p: string): string;
4
+ /**
5
+ * Filter the west manifest to the modules whose sources were compiled in the
6
+ * last build. A module is "linked" iff some compiled translation unit lives
7
+ * under its abspath. If the build data is unparseable, no filtering is applied
8
+ * (degrade to listing all modules rather than reporting nothing).
9
+ */
10
+ export declare function filterLinkedModules(ccText: string, modules: {
11
+ name: string;
12
+ abspath: string;
13
+ }[]): {
14
+ name: string;
15
+ abspath: string;
16
+ }[];
17
+ /**
18
+ * Find the build's compile_commands.json path under `cwd`. Cuttlefish's Zephyr
19
+ * project root is the transpile output dir (e.g. `<cwd>/src/out`), so the
20
+ * build dir is `<root>/build` — not necessarily `<cwd>/build`. Check the
21
+ * common layouts, then fall back to a bounded search (skipping
22
+ * node_modules/.git/dist and dot-directories). Returns the PATH (the caller
23
+ * decides whether to read it); undefined when no build exists.
24
+ */
25
+ export declare function findCompileCommandsPath(readFile: ReadFile, readdir: ReadDir, cwd?: string): string | undefined;
@@ -0,0 +1,97 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-zephyr — shared west workspace ground truth
3
+ //
4
+ // The as-built inventory both `typecad-hal licenses` and `typecad-hal sbom`
5
+ // report from: which west manifest modules the last build ACTUALLY compiled
6
+ // (linked-module filtering via the build's compile_commands.json), plus the
7
+ // build-directory discovery that locates it. A west manifest carries every
8
+ // vendor HAL/library; almost none are linked by a single project — both the
9
+ // license table and the SBOM scope to the linked set by default, so they stay
10
+ // honest about what is actually inside the firmware.
11
+ //
12
+ // Moved out of licenses.ts when the sbom command grew a second consumer; the
13
+ // behavior (case-insensitive, separator-agnostic path matching; degrade to
14
+ // "no filtering" on unparseable build data) is unchanged.
15
+ // ---------------------------------------------------------------------------
16
+ import * as path from 'node:path';
17
+ /** Normalize a path for case-insensitive, separator-agnostic comparison. */
18
+ export function norm(p) {
19
+ return p.split('\\').join('/').toLowerCase();
20
+ }
21
+ /**
22
+ * Filter the west manifest to the modules whose sources were compiled in the
23
+ * last build. A module is "linked" iff some compiled translation unit lives
24
+ * under its abspath. If the build data is unparseable, no filtering is applied
25
+ * (degrade to listing all modules rather than reporting nothing).
26
+ */
27
+ export function filterLinkedModules(ccText, modules) {
28
+ let arr;
29
+ try {
30
+ arr = JSON.parse(ccText);
31
+ }
32
+ catch {
33
+ return modules;
34
+ }
35
+ if (!Array.isArray(arr))
36
+ return modules;
37
+ // One normalized blob of every compiled source path; membership is then a
38
+ // substring check per module (O(modules) after an O(TUs) join).
39
+ const blob = arr
40
+ .map((e) => norm(typeof e?.file === 'string' ? e.file : ''))
41
+ .join('\n');
42
+ return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
43
+ }
44
+ /**
45
+ * Find the build's compile_commands.json path under `cwd`. Cuttlefish's Zephyr
46
+ * project root is the transpile output dir (e.g. `<cwd>/src/out`), so the
47
+ * build dir is `<root>/build` — not necessarily `<cwd>/build`. Check the
48
+ * common layouts, then fall back to a bounded search (skipping
49
+ * node_modules/.git/dist and dot-directories). Returns the PATH (the caller
50
+ * decides whether to read it); undefined when no build exists.
51
+ */
52
+ export function findCompileCommandsPath(readFile, readdir, cwd = process.cwd()) {
53
+ const direct = [
54
+ path.join(cwd, 'build', 'compile_commands.json'),
55
+ path.join(cwd, 'out', 'build', 'compile_commands.json'),
56
+ path.join(cwd, 'src', 'out', 'build', 'compile_commands.json'),
57
+ ];
58
+ for (const c of direct) {
59
+ if (readFile(c))
60
+ return c;
61
+ }
62
+ // Bounded recursive search for a build/compile_commands.json.
63
+ const isCc = (p) => {
64
+ const n = norm(p);
65
+ return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
66
+ };
67
+ let found;
68
+ const seen = new Set();
69
+ const walk = (dir, depth) => {
70
+ if (found || depth > 4 || seen.has(dir))
71
+ return;
72
+ seen.add(dir);
73
+ let entries;
74
+ try {
75
+ entries = readdir(dir);
76
+ }
77
+ catch {
78
+ return;
79
+ }
80
+ for (const e of entries) {
81
+ const full = path.join(dir, e);
82
+ if (e === 'compile_commands.json' && isCc(full)) {
83
+ found = full;
84
+ return;
85
+ }
86
+ }
87
+ for (const e of entries) {
88
+ if (found)
89
+ return;
90
+ if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.'))
91
+ continue;
92
+ walk(path.join(dir, e), depth + 1);
93
+ }
94
+ };
95
+ walk(cwd, 0);
96
+ return found;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecad/framework-zephyr",
3
- "version": "1.0.0-alpha.18",
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",
@@ -39,10 +39,10 @@
39
39
  "dry-run": "node installer/install.mjs --dry-run"
40
40
  },
41
41
  "dependencies": {
42
- "@typecad/cuttlefish": "1.0.0-alpha.18"
42
+ "@typecad/cuttlefish": "1.0.0-alpha.19"
43
43
  },
44
44
  "devDependencies": {
45
- "@typecad/hal": "1.0.0-alpha.18",
45
+ "@typecad/hal": "1.0.0-alpha.19",
46
46
  "typescript": "^5.7.3"
47
47
  },
48
48
  "license": "Apache-2.0",