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

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 (56) 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.js +13 -0
  7. package/dist/display/ui-adapter.js +6 -0
  8. package/dist/doctor.d.ts +3 -3
  9. package/dist/doctor.js +56 -29
  10. package/dist/dt-config/kconfig.d.ts +3 -0
  11. package/dist/dt-config/kconfig.js +18 -0
  12. package/dist/dt-config/overlay.js +5 -0
  13. package/dist/framework.manifest.js +37 -14
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +5 -0
  16. package/dist/licenses.d.ts +59 -0
  17. package/dist/licenses.js +347 -0
  18. package/dist/lowering/dac.d.ts +15 -0
  19. package/dist/lowering/dac.js +69 -0
  20. package/dist/lowering/fs.d.ts +16 -0
  21. package/dist/lowering/fs.js +121 -0
  22. package/dist/lowering/hwtimer.d.ts +15 -0
  23. package/dist/lowering/hwtimer.js +84 -0
  24. package/dist/lowering/index.d.ts +4 -1
  25. package/dist/lowering/index.js +12 -3
  26. package/dist/strategy.js +186 -14
  27. package/dist/toolchain/compat.js +10 -1
  28. package/dist/toolchain/env-check.d.ts +93 -0
  29. package/dist/toolchain/env-check.js +190 -0
  30. package/dist/toolchain/scaffold.js +3 -0
  31. package/dist/toolchain/west-discover.d.ts +11 -3
  32. package/dist/toolchain/west-discover.js +80 -6
  33. package/dist/toolchain/west-spawn.js +15 -0
  34. package/package.json +4 -4
  35. package/src/chips/esp32.ts +12 -0
  36. package/src/chips/types.ts +29 -0
  37. package/src/chips/xiao-ble.ts +6 -0
  38. package/src/display/gfx.ts +135 -19
  39. package/src/display/profiles.ts +13 -0
  40. package/src/display/ui-adapter.ts +5 -0
  41. package/src/doctor.ts +77 -56
  42. package/src/dt-config/kconfig.ts +19 -0
  43. package/src/dt-config/overlay.ts +5 -0
  44. package/src/framework.manifest.ts +38 -14
  45. package/src/index.ts +6 -0
  46. package/src/licenses.ts +425 -0
  47. package/src/lowering/dac.ts +82 -0
  48. package/src/lowering/fs.ts +127 -0
  49. package/src/lowering/hwtimer.ts +101 -0
  50. package/src/lowering/index.ts +9 -2
  51. package/src/strategy.ts +180 -14
  52. package/src/toolchain/compat.ts +154 -145
  53. package/src/toolchain/env-check.ts +285 -0
  54. package/src/toolchain/scaffold.ts +3 -0
  55. package/src/toolchain/west-discover.ts +88 -8
  56. package/src/toolchain/west-spawn.ts +15 -0
@@ -56,4 +56,16 @@ export const ESP32_DEVKITC = {
56
56
  // the ESP32 is AMP (dual-image procpu/appcpu), not SMP, by default — so the
57
57
  // dependency is satisfied. Omitted on radioless targets.
58
58
  wifi: { supported: true },
59
+ // DAC: the ESP32 has two 8-bit DAC channels on GPIO25 (channel 1) and GPIO26
60
+ // (channel 2). The Zephyr esp32 DAC driver (drivers/dac/dac_esp32.c) exposes
61
+ // them via the `dac0` node; the lowering emits dac_channel_setup +
62
+ // dac_write_value against DEVICE_DT_GET(DT_NODELABEL(dac0)). The overlay
63
+ // enables the node when the program uses dac.*. ESP32-S3 has no DAC.
64
+ dac: {
65
+ device: 'dac0',
66
+ channels: [
67
+ { pin: 25, channel: 1, resolution: 8 },
68
+ { pin: 26, channel: 2, resolution: 8 },
69
+ ],
70
+ },
59
71
  };
@@ -81,6 +81,18 @@ export interface ZephyrAdcChannel {
81
81
  /** SAADC channel index (AIN0–AIN7). */
82
82
  readonly channel: number;
83
83
  }
84
+ /**
85
+ * A DAC channel: which DAC output a given HAL pin maps to. The lowering emits
86
+ * `dac_channel_setup` + `dac_write_value` against the DAC device node.
87
+ */
88
+ export interface ZephyrDacChannel {
89
+ /** GPIO number (matches the HAL op `pin` field). */
90
+ readonly pin: number;
91
+ /** DAC channel index (ESP32: GPIO25 → 1, GPIO26 → 2). */
92
+ readonly channel: number;
93
+ /** DAC resolution in bits (ESP32 DAC is 8-bit). */
94
+ readonly resolution: number;
95
+ }
84
96
  /**
85
97
  * Pure-data descriptor for a Zephyr board + its SoC's peripheral layout.
86
98
  */
@@ -139,10 +151,28 @@ export interface ZephyrChipDescriptor {
139
151
  /** ADC resolution in bits. */
140
152
  readonly resolution: number;
141
153
  };
154
+ /**
155
+ * DAC: the DAC device node label + the pin→channel map. Present only on chips
156
+ * with a DAC (ESP32 has 2 channels on GPIO25/26; ESP32-S3 and nRF52840 have
157
+ * none). Read by profileDiagnostics to flag dac.* usage on chips without it.
158
+ */
159
+ readonly dac?: {
160
+ readonly device: string;
161
+ readonly channels: readonly ZephyrDacChannel[];
162
+ };
142
163
  /** Watchdog node label, e.g. 'wdt0'. */
143
164
  readonly wdt?: {
144
165
  readonly nodeLabel: string;
145
166
  };
167
+ /**
168
+ * Hardware timers exposed as Zephyr counter devices. `instance` (the HAL
169
+ * hwtimer.* op's instance index) maps to `controllers[instance].nodeLabel`.
170
+ * Omit on chips whose counter nodes are kernel-owned or unavailable; the
171
+ * lowering then lowers to a comment and profileDiagnostics flags usage.
172
+ */
173
+ readonly hwtimer?: {
174
+ readonly controllers: readonly ZephyrBusController[];
175
+ };
146
176
  /**
147
177
  * WiFi capability marker. Present only on chips with a WiFi radio (ESP32-S3).
148
178
  * Read by profileDiagnostics to flag wifi.* usage on chips without a radio.
@@ -58,4 +58,10 @@ export const XIAO_BLE = {
58
58
  ],
59
59
  },
60
60
  wdt: { nodeLabel: 'wdt0' },
61
+ // Hardware timer: nRF RTC1 is the free counter (RTC0 is kernel-owned by the
62
+ // softdevice/clock driver). The hwtimer lowering drives it as a Zephyr
63
+ // counter device (counter_start/stop + a top-value alarm for set_frequency).
64
+ // Verified against the nRF52840 SoC dtsi (rtc0/rtc1 nodes). The kernel uses
65
+ // RTC0 for the system tick; RTC1 is available for application use.
66
+ hwtimer: { controllers: [{ nodeLabel: 'rtc1' }] },
61
67
  };
@@ -10,8 +10,17 @@ export interface DisplayRuntimeResult {
10
10
  readonly fontTable: string;
11
11
  }
12
12
  /**
13
- * Build the display runtime C++ for a profile. The line buffer is one row
14
- * (width pixels × 2 bytes rgb565); fill_rect/draw_rect/draw_text compute into
15
- * it row-by-row and display_write each row. No full framebuffer.
13
+ * Build the display runtime C++ for a profile.
14
+ *
15
+ * Two rendering models, selected by `profile.colorFormat`:
16
+ * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
17
+ * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
18
+ * each row via display_write.
19
+ * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
20
+ * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
21
+ * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
22
+ * display_flush pushes the whole framebuffer. The MONO01 packing is
23
+ * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
24
+ * bit = 0x80>>(x&7).
16
25
  */
17
26
  export declare function buildDisplayRuntime(profile: ZephyrDisplayProfile): DisplayRuntimeResult;
@@ -71,18 +71,23 @@ function fontTableCpp() {
71
71
  return lines.join('\n');
72
72
  }
73
73
  /**
74
- * Build the display runtime C++ for a profile. The line buffer is one row
75
- * (width pixels × 2 bytes rgb565); fill_rect/draw_rect/draw_text compute into
76
- * it row-by-row and display_write each row. No full framebuffer.
74
+ * Build the display runtime C++ for a profile.
75
+ *
76
+ * Two rendering models, selected by `profile.colorFormat`:
77
+ * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
78
+ * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
79
+ * each row via display_write.
80
+ * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
81
+ * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
82
+ * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
83
+ * display_flush pushes the whole framebuffer. The MONO01 packing is
84
+ * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
85
+ * bit = 0x80>>(x&7).
77
86
  */
78
87
  export function buildDisplayRuntime(profile) {
79
88
  const w = profile.width;
80
- const stateLines = [
81
- '// CUTTLEFISH_DISPLAY_BEGIN',
82
- `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
83
- `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
84
- '// CUTTLEFISH_DISPLAY_END',
85
- ];
89
+ const h = profile.height;
90
+ const isMono = profile.colorFormat === 'mono';
86
91
  // Backlight is optional: the overlay emits the DT alias only when a backlight
87
92
  // GPIO is configured. Guard with DT_HAS_ALIAS so this compiles whether or not
88
93
  // the alias exists (DT_NODE_HAS_STATUS(DT_ALIAS(...)) is version-dependent
@@ -90,7 +95,122 @@ export function buildDisplayRuntime(profile) {
90
95
  const blInit = profile.backlight
91
96
  ? `#if DT_HAS_ALIAS(${profile.backlight})\n const struct device* __bl = DEVICE_DT_GET(DT_ALIAS(${profile.backlight}));\n gpio_pin_configure(__bl, 0, GPIO_OUTPUT); gpio_pin_set(__bl, 0, 1);\n#endif`
92
97
  : '';
93
- const helpers = `
98
+ const stateLines = isMono
99
+ ? [
100
+ '// CUTTLEFISH_DISPLAY_BEGIN',
101
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
102
+ `// Mono framebuffer (Zephyr MONO01: horizontal, MSB-first). ${(w + 7) >> 3} bytes/row x ${h} rows.`,
103
+ `static uint8_t __tc_display_fb[((${w} * ${h}) + 7) / 8];`,
104
+ '// CUTTLEFISH_DISPLAY_END',
105
+ ]
106
+ : [
107
+ '// CUTTLEFISH_DISPLAY_BEGIN',
108
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
109
+ `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
110
+ '// CUTTLEFISH_DISPLAY_END',
111
+ ];
112
+ const helpers = isMono
113
+ ? monoHelpers(w, h, blInit)
114
+ : rgb565Helpers(w, blInit);
115
+ const fontTable = fontTableCpp();
116
+ return {
117
+ includes: ['<zephyr/drivers/display.h>'],
118
+ stateLines,
119
+ helpers,
120
+ fontTable,
121
+ };
122
+ }
123
+ /**
124
+ * Mono (OLED) helpers: a full framebuffer + bit-packing. SSD1306-class panels
125
+ * are page-buffered, so draw ops set bits in the framebuffer and display_flush
126
+ * pushes the whole buffer. color != 0 ⇒ lit.
127
+ */
128
+ function monoHelpers(w, h, blInit) {
129
+ const rowBytes = (w + 7) >> 3; // bytes per row (w is byte-aligned for 128-wide panels)
130
+ return `
131
+ // MONO01 pixel packing: byte = (y * ${rowBytes}) + (x >> 3), bit = 0x80 >> (x & 7).
132
+ static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
133
+ if ((x >= ${w}U) || (y >= ${h}U)) { return; }
134
+ uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) * ${rowBytes}U) + (x >> 3));
135
+ uint8_t mask = static_cast<uint8_t>(0x80U >> (x & 7U));
136
+ if (on != 0U) {
137
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
138
+ } else {
139
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
140
+ }
141
+ }
142
+
143
+ static inline void display_init(void) {
144
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
145
+ ${blInit}
146
+ for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
147
+ display_blanking_off(__tc_display);
148
+ }
149
+
150
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
151
+ uint8_t on = (color != 0U) ? 1U : 0U;
152
+ for (uint16_t row = 0; row < rh; row++) {
153
+ for (uint16_t i = 0; i < rw; i++) {
154
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
155
+ }
156
+ }
157
+ }
158
+
159
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
160
+ uint8_t on = (color != 0U) ? 1U : 0U;
161
+ for (uint16_t i = 0; i < rw; i++) {
162
+ __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
163
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
164
+ }
165
+ for (uint16_t row = 1U; row < rh - 1U; row++) {
166
+ __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
167
+ __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
168
+ }
169
+ }
170
+
171
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
172
+ uint8_t on = (color != 0U) ? 1U : 0U;
173
+ uint16_t cx = x;
174
+ for (const char* p = text; *p != 0; p++) {
175
+ uint8_t uc = static_cast<uint8_t>(*p);
176
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
177
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
178
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
179
+ glyph = &__tc_font5x7[uc - 32U][0];
180
+ } else if (uc < static_cast<uint8_t>('0')
181
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
182
+ || uc > static_cast<uint8_t>('Z')) {
183
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
184
+ }
185
+ for (uint16_t col = 0; col < 5U; col++) {
186
+ uint8_t bits = glyph[col];
187
+ for (uint16_t row = 0; row < 7U; row++) {
188
+ if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
189
+ __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
190
+ }
191
+ }
192
+ }
193
+ cx = static_cast<uint16_t>(cx + 6U);
194
+ }
195
+ }
196
+
197
+ static inline void display_flush(void) {
198
+ struct display_buffer_descriptor __desc;
199
+ __desc.buf_size = sizeof(__tc_display_fb); // ${rowBytes} * ${h} bytes
200
+ __desc.width = ${w}U;
201
+ __desc.height = ${h}U;
202
+ __desc.pitch = ${rowBytes}U; // bytes per row
203
+ __desc.frame_incomplete = false;
204
+ (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
205
+ }
206
+ `;
207
+ }
208
+ /**
209
+ * RGB565 (TFT) helpers: a one-row line buffer; each op streams rows via
210
+ * display_write. No full framebuffer (AGENTS.md rendering guardrails).
211
+ */
212
+ function rgb565Helpers(w, blInit) {
213
+ return `
94
214
  // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
95
215
  // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
96
216
  // 2 bytes/pixel) and pushes the one-row line buffer.
@@ -161,11 +281,4 @@ static inline void display_flush(void) {
161
281
  // No-op: writes are immediate via display_write; there is no framebuffer to push.
162
282
  }
163
283
  `;
164
- const fontTable = fontTableCpp();
165
- return {
166
- includes: ['<zephyr/drivers/display.h>'],
167
- stateLines,
168
- helpers,
169
- fontTable,
170
- };
171
284
  }
@@ -36,6 +36,19 @@ export const ZEPHYR_DISPLAY_PROFILES = {
36
36
  rotation: 1,
37
37
  backlight: 'backlight',
38
38
  },
39
+ 'ssd1306-zephyr': {
40
+ // Monochrome OLED (SSD1306-class, 128x64, 1bpp). Driven through Zephyr's
41
+ // generic display API (the ssd1306 driver + a DT display node). The GFX
42
+ // runtime (gfx.ts mono branch) keeps a full page-framebuffer and pushes it
43
+ // on display_flush — the standard model for page-buffered OLEDs. Direct
44
+ // display.* ops only (no @typecad/ui CuttlefishGFX rendering on mono).
45
+ driver: 'ssd1306-zephyr',
46
+ dtLabel: 'display0',
47
+ width: 128,
48
+ height: 64,
49
+ colorFormat: 'mono',
50
+ rotation: 0,
51
+ },
39
52
  };
40
53
  /** The default profile used when resolveDisplayOp is probed without a display.init. */
41
54
  export const DEFAULT_ZEPHYR_DISPLAY_PROFILE = ZEPHYR_DISPLAY_PROFILES['ili9341-zephyr'];
@@ -527,6 +527,12 @@ export const zephyrDisplayAdapterGenerator = (display) => {
527
527
  const profile = ZEPHYR_DISPLAY_PROFILES[display.driver];
528
528
  if (!profile)
529
529
  return undefined;
530
+ // The UI adapter is RGB565/SPI (TFT) only. Monochrome panels (OLED) use the
531
+ // direct display.* GFX runtime (gfx.ts mono branch) — there is no
532
+ // CuttlefishGFX UI rendering path for mono. Decline so cuttlefish does not
533
+ // emit an incompatible RGB565 adapter for a mono profile.
534
+ if (profile.colorFormat === 'mono')
535
+ return undefined;
530
536
  return zephyrUiDisplayAdapter(profile, {
531
537
  scanlineSync: display.scanlineSync,
532
538
  miso: display.spiPins?.miso,
package/dist/doctor.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Verify the installed Zephyr is reachable + inside the supported range, and
3
- * preview board-target normalization for the configured target. Sets
4
- * process.exitCode = 1 on an out-of-range Zephyr.
2
+ * Verify west is installed + responsive, the Zephyr RTOS is inside the supported
3
+ * range, and the configured board target exists in the checkout. Sets
4
+ * process.exitCode = 1 on failure. Thin presenter over checkZephyrEnv.
5
5
  */
6
6
  export declare function runDoctor(): void;
package/dist/doctor.js CHANGED
@@ -1,48 +1,75 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // @typecad/framework-zephyr — Zephyr environment doctor
3
3
  //
4
- // `cuttlefish doctor` (Zephyr framework) — verify the installed Zephyr RTOS is
5
- // reachable and inside the framework's declared compat range, and preview how
6
- // the configured board target resolves for that version. Exits 0 if the
7
- // environment is OK, non-zero otherwise. Mirrors framework-arduino's doctor
8
- // shape (dispatched via the framework's `doctor` export).
4
+ // `cuttlefish doctor` (Zephyr framework) — verify west (the Zephyr build tool)
5
+ // is installed + responsive, the Zephyr RTOS is inside the framework's declared
6
+ // compat range, and the configured board target exists in the checkout. Exits 0
7
+ // if the environment is OK, non-zero otherwise. Mirrors framework-arduino's
8
+ // doctor shape (dispatched via the framework's `doctor` export) and reuses
9
+ // checkZephyrEnv so the detection logic can be shared with the build/test gates.
9
10
  // ---------------------------------------------------------------------------
10
11
  import * as ui from '@typecad/cuttlefish/utils/ui';
11
12
  import { loadCuttlefishConfig } from '@typecad/cuttlefish/config-loader';
12
- import { detectZephyrVersion, checkZephyrCompat, resolveBoardTarget } from './toolchain/compat.js';
13
+ import { checkZephyrEnv } from './toolchain/env-check.js';
13
14
  /**
14
- * Verify the installed Zephyr is reachable + inside the supported range, and
15
- * preview board-target normalization for the configured target. Sets
16
- * process.exitCode = 1 on an out-of-range Zephyr.
15
+ * Verify west is installed + responsive, the Zephyr RTOS is inside the supported
16
+ * range, and the configured board target exists in the checkout. Sets
17
+ * process.exitCode = 1 on failure. Thin presenter over checkZephyrEnv.
17
18
  */
18
19
  export function runDoctor() {
19
20
  ui.printHeader();
20
21
  ui.printStep('Checking Zephyr environment...');
21
- const base = process.env.ZEPHYR_BASE;
22
- const version = detectZephyrVersion();
23
- const result = checkZephyrCompat(version);
24
- ui.printInfo(`ZEPHYR_BASE ..... ${base ?? '(not set)'}`);
25
- ui.printInfo(`Zephyr version .. ${version ?? 'unknown (could not read ZEPHYR_BASE/VERSION)'}`);
26
- ui.printInfo(`Supported range . ${result.range ?? '(none declared)'}`);
27
- if (result.status === 'out-of-range') {
28
- ui.printError(`Zephyr ${version} is OUTSIDE the supported range (${result.range}).`);
29
- ui.printInfo("Set ZEPHYR_BASE to a compatible Zephyr checkout, or install one via '@typecad/zephyr-installer'.");
30
- process.exitCode = 1;
31
- return;
32
- }
33
- if (result.status === 'undetectable') {
22
+ const config = loadCuttlefishConfig(process.cwd());
23
+ const buildTarget = config?.buildTarget;
24
+ const result = checkZephyrEnv(buildTarget);
25
+ const c = result.check;
26
+ // west (the Zephyr build tool) the analog of arduino-cli presence.
27
+ if (c.westFound) {
28
+ const ver = c.westVersion ?? 'found';
29
+ const src = c.westSource ? ` (${c.westSource})` : '';
30
+ ui.printInfo(`west ............. ${ver} ✓${src}`);
31
+ }
32
+ else {
33
+ ui.printError('west ............. NOT FOUND');
34
+ }
35
+ // Zephyr RTOS version + declared compat range.
36
+ ui.printInfo(`ZEPHYR_BASE ...... ${c.zephyrBase ?? '(not set)'}`);
37
+ ui.printInfo(`Zephyr version .. ${c.zephyrVersion ?? 'unknown (could not read ZEPHYR_BASE/VERSION)'}`);
38
+ ui.printInfo(`Supported range . ${c.compatRange ?? '(none declared)'}`);
39
+ if (c.compatStatus === 'out-of-range') {
40
+ ui.printError(`Zephyr ${c.zephyrVersion} is OUTSIDE the supported range (${c.compatRange}).`);
41
+ }
42
+ else if (c.compatStatus === 'undetectable') {
34
43
  ui.printWarning('Could not detect the Zephyr version (is ZEPHYR_BASE set?) — compat check skipped.');
35
44
  }
36
45
  else {
37
46
  ui.printInfo('Zephyr compat ... OK');
38
47
  }
39
- // Preview how the configured board target resolves for this Zephyr version
40
- // (e.g. a stale bare id would be qualified at build time). The loader extracts
41
- // frameworkData.buildTarget to a top-level field.
42
- const config = loadCuttlefishConfig(process.cwd());
43
- const buildTarget = config?.buildTarget;
48
+ // Board target the analog of the Arduino core presence check.
44
49
  if (buildTarget) {
45
- const resolved = resolveBoardTarget(buildTarget, version);
46
- ui.printInfo(`Board target .... ${buildTarget}${resolved === buildTarget ? '' : ` → ${resolved}`}`);
50
+ const resolved = c.resolvedBoardTarget ?? buildTarget;
51
+ const arrow = resolved === buildTarget ? '' : ` → ${resolved}`;
52
+ if (c.boardTargetSupported === false) {
53
+ ui.printError(`Board target .... ${buildTarget}${arrow} NOT found in this Zephyr checkout`);
54
+ ui.printInfo(' → check the board id, or run: west boards');
55
+ }
56
+ else if (c.boardTargetSupported === undefined) {
57
+ ui.printInfo(`Board target .... ${buildTarget}${arrow}`);
58
+ ui.printInfo('(could not verify board presence — no ZEPHYR_BASE boards/ tree found)');
59
+ }
60
+ else {
61
+ ui.printInfo(`Board target .... ${buildTarget}${arrow}`);
62
+ }
63
+ }
64
+ else {
65
+ ui.printInfo('(no buildTarget in cuttlefish.config.ts — skipping board check)');
66
+ }
67
+ // Exit code — mirrors framework-arduino's doctor.
68
+ if (result.ok) {
69
+ ui.printSuccess('Environment OK');
70
+ return; // exitCode stays unset => 0
47
71
  }
72
+ for (const line of result.messages)
73
+ ui.printInfo(line);
74
+ process.exitCode = 1;
48
75
  }
@@ -1,6 +1,9 @@
1
1
  export interface KconfigUsage {
2
2
  usesAdc?: boolean;
3
3
  usesPwm?: boolean;
4
+ usesDac?: boolean;
5
+ usesFS?: boolean;
6
+ usesHwtimer?: boolean;
4
7
  usesI2c?: boolean;
5
8
  usesSpi?: boolean;
6
9
  usesUart?: boolean;
@@ -23,12 +23,17 @@ export function resolveKconfigFragments(usage, debug) {
23
23
  m.set('CONFIG_ADC', 'y');
24
24
  if (usage.usesPwm)
25
25
  m.set('CONFIG_PWM', 'y');
26
+ if (usage.usesDac)
27
+ m.set('CONFIG_DAC', 'y');
26
28
  if (usage.usesI2c)
27
29
  m.set('CONFIG_I2C', 'y');
28
30
  if (usage.usesSpi)
29
31
  m.set('CONFIG_SPI', 'y');
30
32
  if (usage.usesWdt)
31
33
  m.set('CONFIG_WATCHDOG', 'y');
34
+ // Hardware timers via the counter driver.
35
+ if (usage.usesHwtimer)
36
+ m.set('CONFIG_COUNTER', 'y');
32
37
  if (usage.usesDisplay) {
33
38
  m.set('CONFIG_DISPLAY', 'y');
34
39
  m.set('CONFIG_SPI', 'y');
@@ -237,6 +242,19 @@ export function resolveKconfigFragments(usage, debug) {
237
242
  m.set('CONFIG_SETTINGS', 'y');
238
243
  m.set('CONFIG_SETTINGS_ZMS', 'y');
239
244
  }
245
+ // Filesystem: littlefs on the storage partition. CONFIG_FILE_SYSTEM_LITTLEFS
246
+ // selects the littlefs backend but NOT FLASH/FLASH_MAP (the partition lookup
247
+ // needs them), so all three are set explicitly — same shape as the
248
+ // preferences/ZMS block. The overlay points the storage_partition at the FS
249
+ // (see dt-config/overlay.ts). NOTE: a program using BOTH fs.* and
250
+ // preferences.* shares the one storage_partition between littlefs and ZMS —
251
+ // dedicate separate partitions if both are needed (the manifest flags this).
252
+ if (usage.usesFS) {
253
+ m.set('CONFIG_FLASH', 'y');
254
+ m.set('CONFIG_FLASH_MAP', 'y');
255
+ m.set('CONFIG_FILE_SYSTEM', 'y');
256
+ m.set('CONFIG_FILE_SYSTEM_LITTLEFS', 'y');
257
+ }
240
258
  // usesUart: the board enables the console UART by default; the overlay (not
241
259
  // Kconfig) is where a UART node would be enabled, so no symbol here.
242
260
  // Random: <zephyr/random/random.h> sys_rand_get is backed by the random
@@ -41,6 +41,11 @@ export function generateOverlay(chip, usage, display, wiring, touch) {
41
41
  for (const c of chip.uart.controllers)
42
42
  block(c.nodeLabel);
43
43
  }
44
+ // DAC: enable the chip's DAC device node when the program uses dac.*. The
45
+ // lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)).
46
+ if (usage.usesDac && chip.dac) {
47
+ block(chip.dac.device);
48
+ }
44
49
  if (display) {
45
50
  // Emit a full display DT node definition. Boards like the ESP32 devkit
46
51
  // have no display node in their base DT, so a bare `&display0 { status }`
@@ -104,9 +104,13 @@ export default defineFrameworkManifest({
104
104
  },
105
105
  },
106
106
  dac: {
107
- supported: false,
108
- unsupportedReason: 'No DAC lowering implemented in the framework (not applicable on nRF52840; ESP32 variants with DAC not yet wired).',
109
- ops: { 'dac.write': 'unsupported' },
107
+ // ESP32 DAC (2× 8-bit channels on GPIO25/26) via the Zephyr DAC driver
108
+ // (dac_channel_setup + dac_write_value). nRF52840 / ESP32-S3 have no DAC;
109
+ // usage there lowers to a comment and profileDiagnostics flags it
110
+ // (zephyr-dac-pin-unavailable).
111
+ supported: true,
112
+ partialCoverage: true,
113
+ ops: { 'dac.write': 'supported' },
110
114
  },
111
115
  interrupts: {
112
116
  supported: true,
@@ -278,7 +282,7 @@ export default defineFrameworkManifest({
278
282
  supported: true,
279
283
  partialCoverage: false,
280
284
  unsupportedReason: undefined,
281
- drivers: ['ili9341-zephyr', 'st7796-zephyr'],
285
+ drivers: ['ili9341-zephyr', 'st7796-zephyr', 'ssd1306-zephyr'],
282
286
  colorFormat: 'rgb565',
283
287
  ops: {
284
288
  'display.init': 'supported',
@@ -356,10 +360,17 @@ export default defineFrameworkManifest({
356
360
  },
357
361
  },
358
362
  fs: {
359
- supported: false,
360
- unsupportedReason: 'No filesystem lowering on Zephyr (Zephyr has its own FS API; not wired).',
363
+ // littlefs on the board's storage_partition, via <zephyr/fs/fs.h>. The
364
+ // shim mounts at /lfs lazily (formats on first use) and the HAL paths are
365
+ // treated as paths within the filesystem. Requires CONFIG_FILE_SYSTEM +
366
+ // CONFIG_FILE_SYSTEM_LITTLEFS (emitted by the scaffold when fs.* is used)
367
+ // and the storage_partition node.
368
+ supported: true,
361
369
  partialCoverage: false,
362
- ops: unsupportedOps('fs.'),
370
+ ops: {
371
+ 'fs.begin': 'supported', 'fs.read_text': 'supported', 'fs.write_text': 'supported',
372
+ 'fs.exists': 'supported', 'fs.remove': 'supported',
373
+ },
363
374
  },
364
375
  mdns: {
365
376
  supported: false,
@@ -396,10 +407,17 @@ export default defineFrameworkManifest({
396
407
  ops: { 'temp.read': 'unsupported' },
397
408
  },
398
409
  hwtimer: {
399
- supported: false,
400
- unsupportedReason: 'No hardware-timer lowering on Zephyr (timers are handled via the k_timer polyfill, not hwtimer.*).',
401
- partialCoverage: false,
402
- ops: unsupportedOps('hwtimer.'),
410
+ // Hardware timers via the Zephyr counter driver (<zephyr/drivers/counter.h>).
411
+ // set_frequency top value (counter_freq/hz) + on_overflow callback;
412
+ // start arms both; stop halts. A chip declares its free counters
413
+ // (e.g. nRF RTC1; RTC0 is kernel-owned). This is distinct from the JS
414
+ // setInterval/setTimeout k_timer polyfill, which is unaffected.
415
+ supported: true,
416
+ partialCoverage: true,
417
+ ops: {
418
+ 'hwtimer.set_frequency': 'supported', 'hwtimer.on_overflow': 'supported',
419
+ 'hwtimer.start': 'supported', 'hwtimer.stop': 'supported',
420
+ },
403
421
  },
404
422
  capacitive: {
405
423
  // FT6336U capacitive touch is handled via the strategy-owned touch adapter
@@ -466,6 +484,8 @@ export default defineFrameworkManifest({
466
484
  polyfills: {
467
485
  emitted: [
468
486
  { id: 'cuttlefish_halt', domain: 'standard', notes: 'Mapped to a k_msleep halt loop (exceptions disabled)' },
487
+ { id: 'string_methods', domain: 'embedded', notes: 'STL-free __tc_* string helpers (const char*, inline ASCII case conv, <cstring> only)' },
488
+ { id: 'static_array', domain: 'embedded', notes: 'STL-free __tc_StaticArray<T,N> wrapper for no-<vector> mutated/struct array literals' },
469
489
  { id: 'timer_methods', domain: 'embedded', notes: 'k_timer + k_work pool (system workqueue); callbacks run in thread context' },
470
490
  { id: 'async_runtime', domain: 'embedded', notes: 'Heap-free static Promise/microtask runtime (generateStaticAsyncRuntime), pumped in loop()' },
471
491
  ],
@@ -517,9 +537,9 @@ export default defineFrameworkManifest({
517
537
  // pure string-snapshot tests (no hardware); they are the safety net that
518
538
  // catches regressions like silent pull-resistor / interrupt no-ops.
519
539
  halResolutionTests: [
520
- 'adc', 'ble', 'board', 'dac', 'gpio', 'http', 'i2c', 'interrupts', 'mqtt',
521
- 'power', 'preferences', 'pulse', 'pwm', 'random', 'spi', 'timing', 'tone',
522
- 'uart', 'wdt', 'worker',
540
+ 'adc', 'ble', 'board', 'dac', 'fs', 'gpio', 'http', 'hwtimer', 'i2c',
541
+ 'interrupts', 'mqtt', 'power', 'preferences', 'pulse', 'pwm', 'random',
542
+ 'spi', 'timing', 'tone', 'uart', 'wdt', 'worker',
523
543
  ],
524
544
  },
525
545
  // Declared compatibility range for the installed Zephyr RTOS. The framework's
@@ -533,4 +553,7 @@ export default defineFrameworkManifest({
533
553
  // `cuttlefish doctor` prints the detected Zephyr version + compat result and
534
554
  // previews how the configured board target resolves for that version.
535
555
  doctor: { available: true },
556
+ // `cuttlefish licenses` enumerates the Zephyr kernel + west manifest projects
557
+ // and resolves each one's SPDX license (mirrors framework-arduino).
558
+ licenses: { available: true },
536
559
  });
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ export { ZephyrStrategy as FrameworkStrategy } from './strategy.js';
2
2
  export { ZephyrStrategy } from './strategy.js';
3
3
  export { Toolchain } from './toolchain/index.js';
4
4
  export { runDoctor as doctor } from './doctor.js';
5
+ export { runLicensesPresenter as licenses } from './licenses.js';
5
6
  export { chipForTarget, setActiveChip, getActiveChip, XIAO_BLE, } from './chips/index.js';
6
7
  export type { ZephyrChipDescriptor, ZephyrGpioDtSpec, } from './chips/types.js';
package/dist/index.js CHANGED
@@ -13,5 +13,10 @@ export { Toolchain } from './toolchain/index.js';
13
13
  // under the dispatcher-facing alias `doctor` so the loader picks it up as
14
14
  // mod.doctor (see framework-package.ts).
15
15
  export { runDoctor as doctor } from './doctor.js';
16
+ // `cuttlefish licenses` — enumerate the Zephyr kernel + west manifest projects
17
+ // and resolve each one's SPDX license. Re-exported under the dispatcher-facing
18
+ // alias `licenses` so the loader picks it up as mod.licenses (see
19
+ // framework-package.ts). Mirrors framework-arduino's presenter.
20
+ export { runLicensesPresenter as licenses } from './licenses.js';
16
21
  // Chip descriptor registry (for downstream tooling / additional boards).
17
22
  export { chipForTarget, setActiveChip, getActiveChip, XIAO_BLE, } from './chips/index.js';