@typecad/framework-zephyr 1.0.0-alpha.10 → 1.0.0-alpha.12

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 (67) hide show
  1. package/dist/chips/esp32.js +12 -0
  2. package/dist/chips/types.d.ts +30 -0
  3. package/dist/chips/xiao-ble.js +6 -0
  4. package/dist/display/gfx.d.ts +12 -3
  5. package/dist/display/gfx.js +130 -17
  6. package/dist/display/profiles.d.ts +25 -0
  7. package/dist/display/profiles.js +30 -0
  8. package/dist/display/touch-adapter.d.ts +3 -4
  9. package/dist/display/touch-adapter.js +119 -16
  10. package/dist/display/ui-adapter.js +308 -139
  11. package/dist/doctor.d.ts +3 -3
  12. package/dist/doctor.js +56 -29
  13. package/dist/dt-config/kconfig.d.ts +8 -1
  14. package/dist/dt-config/kconfig.js +40 -5
  15. package/dist/dt-config/overlay.d.ts +18 -1
  16. package/dist/dt-config/overlay.js +124 -26
  17. package/dist/framework.manifest.d.ts +29 -28
  18. package/dist/framework.manifest.js +46 -17
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +5 -0
  21. package/dist/licenses.d.ts +59 -0
  22. package/dist/licenses.js +347 -0
  23. package/dist/lowering/ble.js +3 -1
  24. package/dist/lowering/dac.d.ts +15 -0
  25. package/dist/lowering/dac.js +69 -0
  26. package/dist/lowering/fs.d.ts +16 -0
  27. package/dist/lowering/fs.js +121 -0
  28. package/dist/lowering/hwtimer.d.ts +15 -0
  29. package/dist/lowering/hwtimer.js +84 -0
  30. package/dist/lowering/index.d.ts +4 -1
  31. package/dist/lowering/index.js +12 -3
  32. package/dist/strategy.d.ts +4 -0
  33. package/dist/strategy.js +275 -35
  34. package/dist/toolchain/compat.js +10 -1
  35. package/dist/toolchain/env-check.d.ts +93 -0
  36. package/dist/toolchain/env-check.js +190 -0
  37. package/dist/toolchain/index.js +39 -9
  38. package/dist/toolchain/scaffold.js +7 -2
  39. package/dist/toolchain/west-discover.d.ts +11 -3
  40. package/dist/toolchain/west-discover.js +84 -7
  41. package/dist/toolchain/west-spawn.js +15 -0
  42. package/package.json +4 -4
  43. package/src/chips/esp32.ts +12 -0
  44. package/src/chips/types.ts +29 -0
  45. package/src/chips/xiao-ble.ts +6 -0
  46. package/src/display/gfx.ts +135 -19
  47. package/src/display/profiles.ts +53 -0
  48. package/src/display/touch-adapter.ts +119 -15
  49. package/src/display/ui-adapter.ts +311 -139
  50. package/src/doctor.ts +77 -56
  51. package/src/dt-config/kconfig.ts +45 -6
  52. package/src/dt-config/overlay.ts +159 -29
  53. package/src/framework.manifest.ts +47 -17
  54. package/src/index.ts +6 -0
  55. package/src/licenses.ts +425 -0
  56. package/src/lowering/ble.ts +3 -1
  57. package/src/lowering/dac.ts +82 -0
  58. package/src/lowering/fs.ts +127 -0
  59. package/src/lowering/hwtimer.ts +101 -0
  60. package/src/lowering/index.ts +9 -2
  61. package/src/strategy.ts +271 -35
  62. package/src/toolchain/compat.ts +154 -145
  63. package/src/toolchain/env-check.ts +285 -0
  64. package/src/toolchain/index.ts +40 -9
  65. package/src/toolchain/scaffold.ts +7 -2
  66. package/src/toolchain/west-discover.ts +92 -9
  67. package/src/toolchain/west-spawn.ts +15 -0
@@ -206,12 +206,17 @@ export const Toolchain = {
206
206
  // for either driver. Thread a non-default profile here only if a future
207
207
  // board carries a display node under a different nodelabel.
208
208
  const displayProfile = usesDisplay ? DEFAULT_ZEPHYR_DISPLAY_PROFILE : undefined;
209
+ // Touch controller kind comes from which DT nodelabel the emitted adapter
210
+ // references (FT6336U on I2C, XPT2046 on the display's SPI bus).
211
+ const usesTouch = uses('ft6336u') || uses('touch_');
212
+ const usesXpt = uses('xpt2046');
209
213
  const overlay = generateOverlay(chip, {
210
214
  usesI2c: uses('i2c_'),
211
215
  usesSpi: uses('spi_'),
212
216
  usesUart: uses('uart_'),
213
217
  usesDisplay,
214
- usesTouch: uses('ft6336u') || uses('touch_'),
218
+ usesTouch: usesTouch || usesXpt,
219
+ touchController: usesXpt ? 'xpt2046' : 'ft6336u',
215
220
  }, displayProfile);
216
221
  const overlayDir = join(projectRoot, 'boards');
217
222
  mkdirSync(overlayDir, { recursive: true });
@@ -303,23 +308,49 @@ export const Toolchain = {
303
308
  backlightPin: typeof dispCfg.backlightPin === 'number' ? dispCfg.backlightPin : undefined,
304
309
  }
305
310
  : undefined;
306
- // Extract touch pin wiring (irq/resetPin/sda/scl) from the config
307
- // display.touch section so the DT overlay wires the I2C bus + touch node.
311
+ // Extract touch pin wiring from the config display.touch section so the
312
+ // DT overlay wires the bus + touch node. I2C (FT6336U) carries
313
+ // irq/resetPin/sda/scl; SPI (XPT2046) carries irq/cs + the calibration
314
+ // range the xptek,xpt2046 binding requires.
308
315
  const touchCfg = dispCfg?.touch as Record<string, unknown> | undefined;
309
- const touchWiring: TouchWiring | undefined = touchCfg
316
+ const isXpt = touchCfg?.library === 'XPT2046_Touchscreen';
317
+ const touchCal = touchCfg?.calibration as
318
+ { xMin?: unknown; xMax?: unknown; yMin?: unknown; yMax?: unknown } | undefined;
319
+ const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined);
320
+ let touchWiring: TouchWiring | undefined = touchCfg
310
321
  ? {
311
- irq: typeof touchCfg.irq === 'number' ? touchCfg.irq : undefined,
312
- resetPin: typeof touchCfg.resetPin === 'number' ? touchCfg.resetPin : undefined,
313
- sda: typeof touchCfg.sda === 'number' ? touchCfg.sda : undefined,
314
- scl: typeof touchCfg.scl === 'number' ? touchCfg.scl : undefined,
322
+ controller: isXpt ? 'xpt2046' : 'ft6336u',
323
+ irq: num(touchCfg.irq),
324
+ resetPin: num(touchCfg.resetPin),
325
+ sda: num(touchCfg.sda),
326
+ scl: num(touchCfg.scl),
327
+ cs: num(touchCfg.cs),
328
+ calibration: touchCal
329
+ ? {
330
+ xMin: num(touchCal.xMin) ?? 0,
331
+ xMax: num(touchCal.xMax) ?? 4095,
332
+ yMin: num(touchCal.yMin) ?? 0,
333
+ yMax: num(touchCal.yMax) ?? 4095,
334
+ }
335
+ : undefined,
336
+ minPressure: num(touchCfg.minPressure),
315
337
  }
316
338
  : undefined;
339
+ // Touch controller kind for Kconfig (bus driver selection) and the DT
340
+ // node shape: from the config when available, else from the DT nodelabel
341
+ // the emitted adapter references. Forced onto touchWiring so a source
342
+ // scan match without a config section still emits the right node.
343
+ const usesXpt = isXpt || uses('xpt2046');
344
+ if (usesXpt) {
345
+ touchWiring = { controller: 'xpt2046', ...(touchWiring ?? {}) };
346
+ }
317
347
  const overlay = generateOverlay(chip, {
318
348
  usesI2c: uses('i2c_'),
319
349
  usesSpi: uses('spi_'),
320
350
  usesUart: uses('uart_'),
321
351
  usesDisplay,
322
- usesTouch: uses('ft6336u') || uses('touch_'),
352
+ usesTouch: uses('ft6336u') || uses('touch_') || usesXpt,
353
+ touchController: usesXpt ? 'xpt2046' : 'ft6336u',
323
354
  psram: o.psram,
324
355
  }, displayProfile, wiring, touchWiring);
325
356
  const overlayDir = join(projectRoot, 'boards');
@@ -85,6 +85,9 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
85
85
  const usage: KconfigUsage = {
86
86
  usesAdc: uses('adc_'),
87
87
  usesPwm: uses('pwm_'),
88
+ usesDac: uses('dac_') || uses('__tc_dac'),
89
+ usesFS: uses('__tc_fs'),
90
+ usesHwtimer: uses('counter_') || uses('__tc_hw'),
88
91
  usesI2c: uses('i2c_'),
89
92
  usesSpi: uses('spi_'),
90
93
  usesUart: uses('uart_'),
@@ -127,8 +130,10 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
127
130
  '',
128
131
  'project(zephyr_app)',
129
132
  '',
130
- '# Collect cuttlefish-emitted sources.',
131
- 'file(GLOB app_sources src/*.cpp src/*.c)',
133
+ '# Collect cuttlefish-emitted sources. CONFIGURE_DEPENDS makes CMake re-',
134
+ '# check the glob when the source set changes (e.g. the transpiler removes',
135
+ '# a stale entry), instead of linking a file list from the last configure.',
136
+ 'file(GLOB app_sources CONFIGURE_DEPENDS src/*.cpp src/*.c)',
132
137
  '',
133
138
  'target_sources(app PRIVATE ${app_sources})',
134
139
  // When PSRAM is configured, define BOARD_HAS_PSRAM so the UI runtime's
@@ -10,15 +10,17 @@
10
10
  // Discovery cascade (first usable wins):
11
11
  // 1. `west` already on PATH (env already activated / global install).
12
12
  // 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
13
- // 3. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
13
+ // 3. micromamba env from @typecad/zephyr-installer (invoked via `micromamba run`,
14
+ // so cuttlefish builds work with NO manual activation).
15
+ // 4. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
14
16
  // venv, etc.
15
- // 4. System pythons (`python`, `python3`, `py`) via `-m west`.
17
+ // 5. System pythons (`python`, `python3`, `py`) via `-m west`.
16
18
  //
17
19
  // Leaner than the ESP-IDF equivalent: west needs no env sourcing (no 15s
18
20
  // export.sh) — only the right interpreter + ZEPHYR_BASE.
19
21
  // ---------------------------------------------------------------------------
20
22
 
21
- import { existsSync, realpathSync, statSync } from 'node:fs';
23
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
22
24
  import { dirname, join } from 'node:path';
23
25
  import { homedir } from 'node:os';
24
26
  import { spawnSync } from 'node:child_process';
@@ -65,15 +67,21 @@ function westOnPath(cmd: string): boolean {
65
67
  * `pythonExecutable -m west`.
66
68
  */
67
69
  export interface WestInstall {
68
- mode: 'launcher' | 'module';
70
+ mode: 'launcher' | 'module' | 'micromamba';
69
71
  /** Absolute path to a `west` launcher (mode 'launcher') or undefined. */
70
72
  westExecutable?: string;
71
73
  /** Absolute path to a Python interpreter with west installed (mode 'module'). */
72
74
  pythonExecutable?: string;
73
75
  /** Absolute path to the Zephyr SDK root (for $ZEPHYR_BASE), if found. */
74
76
  zephyrBase?: string;
77
+ /** mode 'micromamba': path to the micromamba binary (for `micromamba run -n …`). */
78
+ micromambaExe?: string;
79
+ /** mode 'micromamba': the conda env name (default 'zephyr'). */
80
+ envName?: string;
81
+ /** mode 'micromamba': MAMBA_ROOT_PREFIX, injected so micromamba finds its envs. */
82
+ mambaRootPrefix?: string;
75
83
  /** Which discovery strategy found this install. */
76
- source: 'path' | 'zephyr-base-venv' | 'well-known' | 'system-python';
84
+ source: 'path' | 'zephyr-base-venv' | 'well-known' | 'system-python' | 'micromamba';
77
85
  }
78
86
 
79
87
  /** True if `dir` looks like a Zephyr SDK root: has CMakeLists.txt and the
@@ -89,9 +97,12 @@ export function isZephyrBase(dir: string): boolean {
89
97
  // ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
90
98
 
91
99
  export function discoverFromPath(): WestInstall | null {
100
+ // shell only on Windows (where.exe resolution through cmd) — an args array
101
+ // with shell: true triggers Node's DEP0190 deprecation warning on Linux/
102
+ // macOS, where `which` is a plain executable that needs no shell.
92
103
  const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
93
104
  encoding: 'utf8',
94
- shell: true,
105
+ shell: IS_WIN,
95
106
  windowsHide: true,
96
107
  });
97
108
  if (which.status !== 0) return null;
@@ -128,7 +139,77 @@ export function discoverFromZephyrBase(): WestInstall | null {
128
139
  };
129
140
  }
130
141
 
131
- // ── Strategy 3: well-known workspace layouts ───────────────────────────────
142
+ // ── Strategy 3: micromamba env (the @typecad/zephyr-installer install) ─────
143
+
144
+ // Locate the micromamba binary + root prefix. The installer downloads
145
+ // micromamba to $MAMBA_ROOT_PREFIX/bin (POSIX) or Library/bin (Windows); the
146
+ // root defaults to ~/micromamba. Returns null if the binary isn't present
147
+ // (the installer hasn't run on this machine).
148
+ function findMicromamba(): { exe: string; rootPrefix: string } | null {
149
+ const root = process.env.MAMBA_ROOT_PREFIX || join(homedir(), 'micromamba');
150
+ const exe = IS_WIN
151
+ ? join(root, 'Library', 'bin', 'micromamba.exe')
152
+ : join(root, 'bin', 'micromamba');
153
+ return existsSync(exe) ? { exe, rootPrefix: root } : null;
154
+ }
155
+
156
+ /**
157
+ * The micromamba env created by `@typecad/zephyr-installer`. The env's west
158
+ * lives at envs/<name>/bin/west (POSIX) or Scripts/west.exe (Windows). Found
159
+ * installs are invoked via `micromamba run -n <name> west …` (see
160
+ * west-spawn.ts), which sets up the env's full PATH (cmake/ninja/dtc) AND runs
161
+ * the activation hook (ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR) — so cuttlefish
162
+ * builds work with NO manual `micromamba activate`. This is what makes a fresh
163
+ * `cuttlefish build` succeed in any project without the user activating.
164
+ *
165
+ * Env name defaults to "zephyr"; override via TYPECAD_ZEPHYR_ENV. File-check
166
+ * based (no spawn) so it's cheap to run on every cuttlefish invocation.
167
+ */
168
+ /** Read a TYPECAD_ZEPHYR_* value from the installer-written env-vars file in
169
+ * a micromamba env. Handles .sh (export VAR="val"), .bat (set "VAR=val"),
170
+ * and .ps1 ($env:VAR = "val"). Returns undefined if absent/unreadable. */
171
+ function readMicromambaEnvVar(envDir: string, varName: string): string | undefined {
172
+ const candidates = IS_WIN
173
+ ? [join(envDir, 'etc', 'conda', 'env-vars.ps1'), join(envDir, 'etc', 'conda', 'env-vars.bat')]
174
+ : [join(envDir, 'etc', 'conda', 'env-vars.sh')];
175
+ for (const f of candidates) {
176
+ if (!existsSync(f)) continue;
177
+ try {
178
+ const text = readFileSync(f, 'utf8');
179
+ // .sh/.ps1: VAR = "value" (quoted value after =).
180
+ let m = text.match(new RegExp(`${varName}\\s*=\\s*"([^"]+)"`));
181
+ if (m) return m[1];
182
+ // .bat: set "VAR=value" (value after VAR= inside quotes).
183
+ m = text.match(new RegExp(`${varName}=([^"\\r\\n]+)"`));
184
+ if (m) return m[1].trim();
185
+ } catch { /* ignore unreadable */ }
186
+ }
187
+ return undefined;
188
+ }
189
+
190
+ export function discoverFromMicromamba(
191
+ envName: string = process.env.TYPECAD_ZEPHYR_ENV || 'zephyr',
192
+ ): WestInstall | null {
193
+ const mm = findMicromamba();
194
+ if (!mm) return null;
195
+ const envDir = join(mm.rootPrefix, 'envs', envName);
196
+ const westExe = join(envDir, IS_WIN ? 'Scripts' : 'bin', IS_WIN ? 'west.exe' : 'west');
197
+ if (!existsSync(envDir) || !existsSync(westExe)) return null;
198
+ // Read ZEPHYR_BASE from the installer's env-vars so the compat check (and
199
+ // anything else in the cuttlefish process) can detect the Zephyr version
200
+ // WITHOUT activation — micromamba run sets it only inside the west subprocess.
201
+ const zb = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_BASE');
202
+ return {
203
+ mode: 'micromamba',
204
+ micromambaExe: mm.exe,
205
+ envName,
206
+ mambaRootPrefix: mm.rootPrefix,
207
+ zephyrBase: zb && isZephyrBase(zb) ? zb : undefined,
208
+ source: 'micromamba',
209
+ };
210
+ }
211
+
212
+ // ── Strategy 4: well-known workspace layouts ───────────────────────────────
132
213
 
133
214
  /** Candidate Zephyr workspace directories. Each may contain both `.venv/`
134
215
  * and `zephyr/` (the SDK). Exported for test injection. */
@@ -169,7 +250,7 @@ export function discoverFromWellKnown(
169
250
  return null;
170
251
  }
171
252
 
172
- // ── Strategy 4: system pythons via `-m west` ────────────────────────────────
253
+ // ── Strategy 5: system pythons via `-m west` ────────────────────────────────
173
254
 
174
255
  /** Candidate system Python interpreters to probe with `-m west`. */
175
256
  export function systemPythons(): string[] {
@@ -205,7 +286,8 @@ export function resetWestDiscoveryCache(): void {
205
286
  * Try each discovery strategy in order. The first usable install wins.
206
287
  * Result is memoized for the process lifetime (west installs don't move).
207
288
  *
208
- * Order: PATH → $ZEPHYR_BASE venv → well-known workspaces → system pythons.
289
+ * Order: PATH → $ZEPHYR_BASE venv → micromamba env → well-known workspaces →
290
+ * system pythons.
209
291
  * Returns null when no usable west install is found.
210
292
  */
211
293
  export function discoverWest(): WestInstall | null {
@@ -213,6 +295,7 @@ export function discoverWest(): WestInstall | null {
213
295
  const strategies: Array<() => WestInstall | null> = [
214
296
  discoverFromPath,
215
297
  discoverFromZephyrBase,
298
+ discoverFromMicromamba,
216
299
  discoverFromWellKnown,
217
300
  discoverFromSystemPython,
218
301
  ];
@@ -121,6 +121,21 @@ export function westSpawn(
121
121
  // an explicit shell changes arg-quoting semantics on Windows.
122
122
  const { shell: _drop, ...optsWithoutShell } = baseOptions as any;
123
123
 
124
+ if (install.mode === 'micromamba' && install.micromambaExe) {
125
+ // `micromamba run -n <env> west …` sets up the env's full PATH
126
+ // (cmake/ninja/dtc) and runs the activation hook (ZEPHYR_BASE /
127
+ // ZEPHYR_SDK_INSTALL_DIR), so cuttlefish builds work WITHOUT the user
128
+ // activating the env. Inject MAMBA_ROOT_PREFIX so micromamba finds envs.
129
+ const mmEnv = { ...env };
130
+ if (install.mambaRootPrefix) mmEnv.MAMBA_ROOT_PREFIX = install.mambaRootPrefix;
131
+ return {
132
+ command: install.micromambaExe,
133
+ args: ['run', '-n', install.envName ?? 'zephyr', 'west', ...westArgs],
134
+ options: { ...optsWithoutShell, env: mmEnv },
135
+ install,
136
+ };
137
+ }
138
+
124
139
  if (install.mode === 'launcher' && install.westExecutable) {
125
140
  return {
126
141
  command: install.westExecutable,