@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.
- package/dist/chips/esp32.js +12 -0
- package/dist/chips/types.d.ts +30 -0
- package/dist/chips/xiao-ble.js +6 -0
- package/dist/display/gfx.d.ts +12 -3
- package/dist/display/gfx.js +130 -17
- package/dist/display/profiles.js +13 -0
- package/dist/display/ui-adapter.js +6 -0
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +3 -0
- package/dist/dt-config/kconfig.js +18 -0
- package/dist/dt-config/overlay.js +5 -0
- package/dist/framework.manifest.js +37 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/licenses.d.ts +59 -0
- package/dist/licenses.js +347 -0
- package/dist/lowering/dac.d.ts +15 -0
- package/dist/lowering/dac.js +69 -0
- package/dist/lowering/fs.d.ts +16 -0
- package/dist/lowering/fs.js +121 -0
- package/dist/lowering/hwtimer.d.ts +15 -0
- package/dist/lowering/hwtimer.js +84 -0
- package/dist/lowering/index.d.ts +4 -1
- package/dist/lowering/index.js +12 -3
- package/dist/strategy.js +186 -14
- package/dist/toolchain/compat.js +10 -1
- package/dist/toolchain/env-check.d.ts +93 -0
- package/dist/toolchain/env-check.js +190 -0
- package/dist/toolchain/scaffold.js +3 -0
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +80 -6
- package/dist/toolchain/west-spawn.js +15 -0
- package/package.json +4 -4
- package/src/chips/esp32.ts +12 -0
- package/src/chips/types.ts +29 -0
- package/src/chips/xiao-ble.ts +6 -0
- package/src/display/gfx.ts +135 -19
- package/src/display/profiles.ts +13 -0
- package/src/display/ui-adapter.ts +5 -0
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +19 -0
- package/src/dt-config/overlay.ts +5 -0
- package/src/framework.manifest.ts +38 -14
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/dac.ts +82 -0
- package/src/lowering/fs.ts +127 -0
- package/src/lowering/hwtimer.ts +101 -0
- package/src/lowering/index.ts +9 -2
- package/src/strategy.ts +180 -14
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/scaffold.ts +3 -0
- package/src/toolchain/west-discover.ts +88 -8
- package/src/toolchain/west-spawn.ts +15 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Hardware-timer lowering — Zephyr counter API
|
|
3
|
+
//
|
|
4
|
+
// HAL `hwtimer.*` (instance → frequency/overflow/start/stop) maps onto Zephyr's
|
|
5
|
+
// counter driver (<zephyr/drivers/counter.h>). A chip declares the counter
|
|
6
|
+
// devices it exposes (e.g. nRF RTC1 — RTC0 is kernel-owned) via
|
|
7
|
+
// `hwtimer.controllers[instance].nodeLabel`.
|
|
8
|
+
//
|
|
9
|
+
// Frequency model: a Zephyr counter has a fixed clock; the HAL
|
|
10
|
+
// set_frequency(hz) is realized as a top value of counter_freq/hz with an alarm
|
|
11
|
+
// callback (the on_overflow handler). start() arms both the top value and the
|
|
12
|
+
// callback so the call order (setFrequency → onOverflow → start, or any
|
|
13
|
+
// permutation) is handled uniformly.
|
|
14
|
+
//
|
|
15
|
+
// Targets without a free counter omit `hwtimer`; usage lowers to a comment and
|
|
16
|
+
// profileDiagnostics flags it. The JS setInterval/setTimeout polyfill
|
|
17
|
+
// (k_timer) is a separate surface and is unaffected.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/** Per-instance emitted symbol stems. */
|
|
20
|
+
function devVar(instance) {
|
|
21
|
+
return `__tc_hw_dev_${instance}`;
|
|
22
|
+
}
|
|
23
|
+
function hzVar(instance) {
|
|
24
|
+
return `__tc_hw_hz_${instance}`;
|
|
25
|
+
}
|
|
26
|
+
function cbVar(instance) {
|
|
27
|
+
return `__tc_hw_cb_${instance}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Emit the per-instance counter device handles + frequency/callback state.
|
|
31
|
+
* Called from shimLines when the program uses hardware timers.
|
|
32
|
+
*/
|
|
33
|
+
export function hwtimerInitLines(chip) {
|
|
34
|
+
const controllers = chip.hwtimer?.controllers ?? [];
|
|
35
|
+
if (controllers.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
const lines = ['// CUTTLEFISH_HWTIMER_BEGIN'];
|
|
38
|
+
controllers.forEach((c, i) => {
|
|
39
|
+
lines.push(`static const struct device* ${devVar(i)} = DEVICE_DT_GET(DT_NODELABEL(${c.nodeLabel}));`, `static uint32_t ${hzVar(i)} = 1;`, `static counter_top_callback_t ${cbVar(i)} = NULL;`);
|
|
40
|
+
});
|
|
41
|
+
lines.push('// CUTTLEFISH_HWTIMER_END');
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a HAL hwtimer.* op to Zephyr C++.
|
|
46
|
+
* Returns `{ code }` for statement ops.
|
|
47
|
+
*/
|
|
48
|
+
export function lowerHwtimer(op, chip) {
|
|
49
|
+
const o = op;
|
|
50
|
+
const controllers = chip.hwtimer?.controllers ?? [];
|
|
51
|
+
// No counter on this target. Comment + (in profileDiagnostics) a clear error.
|
|
52
|
+
if (controllers.length === 0) {
|
|
53
|
+
return { code: `/* hwtimer instance ${o.instance}: no counter device on ${chip.id} */` };
|
|
54
|
+
}
|
|
55
|
+
const instance = typeof o.instance === 'number' ? o.instance : parseInt(String(o.instance), 10);
|
|
56
|
+
if (isNaN(instance) || instance < 0 || instance >= controllers.length) {
|
|
57
|
+
return { code: `/* hwtimer instance ${o.instance}: out of range on ${chip.id} */` };
|
|
58
|
+
}
|
|
59
|
+
switch (op.operation) {
|
|
60
|
+
case 'hwtimer.set_frequency':
|
|
61
|
+
return { code: `${hzVar(instance)} = ${o.hz};` };
|
|
62
|
+
case 'hwtimer.on_overflow':
|
|
63
|
+
return { code: `${cbVar(instance)} = (${o.handler});` };
|
|
64
|
+
case 'hwtimer.start':
|
|
65
|
+
// Arm the top value (counter_freq / desired_hz) + the overflow callback,
|
|
66
|
+
// then start the counter. Doing both here handles any call order — the
|
|
67
|
+
// HAL typical sequence (setFrequency → onOverflow → start) and permutations.
|
|
68
|
+
return {
|
|
69
|
+
code: [
|
|
70
|
+
`{`,
|
|
71
|
+
` uint32_t __f = counter_get_frequency(${devVar(instance)});`,
|
|
72
|
+
` uint32_t __top = __f ? (__f / ${hzVar(instance)}) : 0U;`,
|
|
73
|
+
` if (__top > 0U) { (void)counter_set_top_value(${devVar(instance)}, __top, ${cbVar(instance)}, NULL); }`,
|
|
74
|
+
` (void)counter_start(${devVar(instance)});`,
|
|
75
|
+
`}`,
|
|
76
|
+
].join(' '),
|
|
77
|
+
};
|
|
78
|
+
case 'hwtimer.stop':
|
|
79
|
+
return { code: `(void)counter_stop(${devVar(instance)});` };
|
|
80
|
+
default:
|
|
81
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
82
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/lowering/index.d.ts
CHANGED
|
@@ -18,7 +18,10 @@ import { lowerMqtt } from './mqtt.js';
|
|
|
18
18
|
import { lowerPreferences } from './preferences.js';
|
|
19
19
|
import { lowerBoard } from './board.js';
|
|
20
20
|
import { lowerRandom } from './random.js';
|
|
21
|
-
|
|
21
|
+
import { lowerDac } from './dac.js';
|
|
22
|
+
import { lowerFs } from './fs.js';
|
|
23
|
+
import { lowerHwtimer } from './hwtimer.js';
|
|
24
|
+
export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, };
|
|
22
25
|
/**
|
|
23
26
|
* Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
|
|
24
27
|
* (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
|
package/dist/lowering/index.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Routes a HALOpIR to the per-category lowering module by category prefix
|
|
5
5
|
// (e.g. 'gpio.write' → lowerGpio). Returns undefined for categories the
|
|
6
|
-
// framework does not lower (display/
|
|
7
|
-
//
|
|
6
|
+
// framework does not lower (display/mdns/ota/rmt/
|
|
7
|
+
// capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
|
|
8
8
|
// the manifest), so the transpiler falls back and the manifest validator
|
|
9
9
|
// cross-checks the unsupported categories. (board.* is registered below but is
|
|
10
10
|
// a dead-letter — its values are constant-folded at IR-build time.)
|
|
@@ -36,7 +36,10 @@ import { lowerMqtt } from './mqtt.js';
|
|
|
36
36
|
import { lowerPreferences } from './preferences.js';
|
|
37
37
|
import { lowerBoard } from './board.js';
|
|
38
38
|
import { lowerRandom } from './random.js';
|
|
39
|
-
|
|
39
|
+
import { lowerDac } from './dac.js';
|
|
40
|
+
import { lowerFs } from './fs.js';
|
|
41
|
+
import { lowerHwtimer } from './hwtimer.js';
|
|
42
|
+
export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, };
|
|
40
43
|
/**
|
|
41
44
|
* Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
|
|
42
45
|
* (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
|
|
@@ -51,6 +54,8 @@ export function lowerHalOp(op) {
|
|
|
51
54
|
return lowerAdc(op, chip);
|
|
52
55
|
if (op.operation.startsWith('pwm.'))
|
|
53
56
|
return lowerPwm(op, chip);
|
|
57
|
+
if (op.operation.startsWith('dac.'))
|
|
58
|
+
return lowerDac(op, chip);
|
|
54
59
|
if (op.operation.startsWith('i2c.'))
|
|
55
60
|
return lowerI2c(op, chip);
|
|
56
61
|
if (op.operation.startsWith('spi.'))
|
|
@@ -61,6 +66,8 @@ export function lowerHalOp(op) {
|
|
|
61
66
|
return lowerInterrupt(op, chip);
|
|
62
67
|
if (op.operation.startsWith('wdt.'))
|
|
63
68
|
return lowerWdt(op);
|
|
69
|
+
if (op.operation.startsWith('hwtimer.'))
|
|
70
|
+
return lowerHwtimer(op, chip);
|
|
64
71
|
if (op.operation.startsWith('power.'))
|
|
65
72
|
return lowerPower(op);
|
|
66
73
|
if (op.operation.startsWith('tone.'))
|
|
@@ -80,6 +87,8 @@ export function lowerHalOp(op) {
|
|
|
80
87
|
return lowerMqtt(op);
|
|
81
88
|
if (op.operation.startsWith('preferences.'))
|
|
82
89
|
return lowerPreferences(op);
|
|
90
|
+
if (op.operation.startsWith('fs.'))
|
|
91
|
+
return lowerFs(op);
|
|
83
92
|
// board.resolve is constant-folded at IR-build time; lowerBoard is the
|
|
84
93
|
// dead-letter reached only on an unresolvable path.
|
|
85
94
|
if (op.operation.startsWith('board.'))
|
package/dist/strategy.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
// repository root) and are not subject to the license of this tool source.
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
import { buildWorkerRuntimePolyfill } from '@typecad/cuttlefish/api/shared';
|
|
18
|
+
import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
|
|
18
19
|
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
20
|
+
import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
|
|
19
21
|
import { chipForTarget, setActiveChip } from './chips/index.js';
|
|
20
22
|
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
21
23
|
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
@@ -23,6 +25,9 @@ import { lowerHalOp } from './lowering/index.js';
|
|
|
23
25
|
import { buildZephyrWorkerBacking } from './lowering/worker-backing.js';
|
|
24
26
|
import { adcInitLines } from './lowering/adc.js';
|
|
25
27
|
import { pwmInitLines } from './lowering/pwm.js';
|
|
28
|
+
import { dacInitLines } from './lowering/dac.js';
|
|
29
|
+
import { fsInitLines } from './lowering/fs.js';
|
|
30
|
+
import { hwtimerInitLines } from './lowering/hwtimer.js';
|
|
26
31
|
import { i2cInitLines } from './lowering/i2c.js';
|
|
27
32
|
import { spiInitLines } from './lowering/spi.js';
|
|
28
33
|
import { uartInitLines } from './lowering/uart.js';
|
|
@@ -125,6 +130,16 @@ export class ZephyrStrategy {
|
|
|
125
130
|
inc.push('<zephyr/drivers/adc.h>');
|
|
126
131
|
if (uses('usesPWM'))
|
|
127
132
|
inc.push('<zephyr/drivers/pwm.h>');
|
|
133
|
+
if (uses('usesDAC'))
|
|
134
|
+
inc.push('<zephyr/drivers/dac.h>');
|
|
135
|
+
// Filesystem: littlefs on the storage partition. <cstring> backs the
|
|
136
|
+
// shim's strlen; the storage/flash_map + fs/littlefs headers carry the
|
|
137
|
+
// FIXED_PARTITION_ID macro + FS_LITTLEFS_DECLARE_DEFAULT_CONFIG the shim uses.
|
|
138
|
+
if (uses('usesFS'))
|
|
139
|
+
inc.push('<zephyr/fs/fs.h>', '<zephyr/fs/littlefs.h>', '<zephyr/storage/flash_map.h>', '<cstring>');
|
|
140
|
+
// Hardware timers via the counter driver.
|
|
141
|
+
if (uses('usesHwtimer'))
|
|
142
|
+
inc.push('<zephyr/drivers/counter.h>');
|
|
128
143
|
if (uses('usesWDT'))
|
|
129
144
|
inc.push('<zephyr/drivers/watchdog.h>');
|
|
130
145
|
if (uses('usesPower'))
|
|
@@ -290,22 +305,28 @@ export class ZephyrStrategy {
|
|
|
290
305
|
lines.push(...adcInitLines(chip));
|
|
291
306
|
if (uses('usesPWM') && chip.pwm)
|
|
292
307
|
lines.push(...pwmInitLines(chip));
|
|
308
|
+
if (uses('usesDAC') && chip.dac)
|
|
309
|
+
lines.push(...dacInitLines(chip));
|
|
310
|
+
if (uses('usesHwtimer') && chip.hwtimer)
|
|
311
|
+
lines.push(...hwtimerInitLines(chip));
|
|
293
312
|
if (uses('usesInterrupts'))
|
|
294
313
|
lines.push(...interruptInitLines(chip));
|
|
295
314
|
if (uses('usesWDT') && chip.wdt)
|
|
296
315
|
lines.push(...wdtInitLines(chip));
|
|
297
316
|
if (uses('usesBle'))
|
|
298
317
|
lines.push(...bleInitLines());
|
|
299
|
-
// Display runtime (
|
|
300
|
-
//
|
|
301
|
-
// display
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
318
|
+
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
319
|
+
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
320
|
+
// when the program uses display.* but is NOT a UI program — the UI display
|
|
321
|
+
// adapter (emitted by cuttlefish's emitUIRuntime, solely under entryHasUI())
|
|
322
|
+
// defines the same display_init symbol, so emitting both would collide.
|
|
323
|
+
// `providesDisplayAdapter()` is a static capability (always true here) and
|
|
324
|
+
// does NOT track whether the adapter is actually emitted for THIS build, so
|
|
325
|
+
// the per-program UI signal (entryHasUI) is the correct gate. Without this,
|
|
326
|
+
// a direct display.* program has no definition for display_init/
|
|
327
|
+
// display_fill_rect/draw_rect/draw_text/flush (the gfx runtime was
|
|
328
|
+
// previously dead code).
|
|
329
|
+
if (uses('usesDisplay') && !entryHasUI()) {
|
|
309
330
|
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
310
331
|
lines.push(...rt.stateLines);
|
|
311
332
|
lines.push(rt.fontTable);
|
|
@@ -319,6 +340,8 @@ export class ZephyrStrategy {
|
|
|
319
340
|
lines.push(...mqttInitLines());
|
|
320
341
|
if (uses('usesPreferences'))
|
|
321
342
|
lines.push(...preferencesInitLines());
|
|
343
|
+
if (uses('usesFS'))
|
|
344
|
+
lines.push(...fsInitLines());
|
|
322
345
|
if (uses('usesRandom'))
|
|
323
346
|
lines.push(...randomInitLines());
|
|
324
347
|
lines.push('#endif // CUTTLEFISH_SHIM_DEFINED');
|
|
@@ -384,6 +407,8 @@ export class ZephyrStrategy {
|
|
|
384
407
|
const outputPins = new Set();
|
|
385
408
|
const adcReadPins = new Set();
|
|
386
409
|
const interruptPins = new Set();
|
|
410
|
+
const dacPins = new Set();
|
|
411
|
+
const hwtimerInstances = new Set();
|
|
387
412
|
let usesWifiOps = false;
|
|
388
413
|
let usesHttpOps = false;
|
|
389
414
|
let usesMqttOps = false;
|
|
@@ -404,6 +429,16 @@ export class ZephyrStrategy {
|
|
|
404
429
|
if (op.operation === 'interrupt.attach' && typeof op.pin === 'number') {
|
|
405
430
|
interruptPins.add(op.pin);
|
|
406
431
|
}
|
|
432
|
+
if (op.operation === 'dac.write' && typeof op.pin === 'number') {
|
|
433
|
+
dacPins.add(op.pin);
|
|
434
|
+
}
|
|
435
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
|
|
436
|
+
const inst = typeof op.instance === 'number'
|
|
437
|
+
? op.instance
|
|
438
|
+
: parseInt(String(op.instance), 10);
|
|
439
|
+
if (!isNaN(inst))
|
|
440
|
+
hwtimerInstances.add(inst);
|
|
441
|
+
}
|
|
407
442
|
if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
|
|
408
443
|
usesWifiOps = true;
|
|
409
444
|
}
|
|
@@ -464,6 +499,55 @@ export class ZephyrStrategy {
|
|
|
464
499
|
});
|
|
465
500
|
}
|
|
466
501
|
}
|
|
502
|
+
// ── DAC pin validity ────────────────────────────────────────────────────
|
|
503
|
+
// dac.write resolves a HAL pin to a channel via the chip descriptor's
|
|
504
|
+
// dac.channels map. A pin not in that map lowers to a comment (silent
|
|
505
|
+
// no-op), and a chip without a `dac` entry (nRF52840, ESP32-S3) has no DAC
|
|
506
|
+
// at all. Flag either case so the user gets a clear message instead of a
|
|
507
|
+
// pin that silently does nothing.
|
|
508
|
+
if (dacPins.size > 0) {
|
|
509
|
+
const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
|
|
510
|
+
for (const pin of dacPins) {
|
|
511
|
+
if (!dacChannels.has(pin)) {
|
|
512
|
+
diags.push({
|
|
513
|
+
severity: 'error',
|
|
514
|
+
code: 'zephyr-dac-pin-unavailable',
|
|
515
|
+
message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
|
|
516
|
+
hint: dacChannels.size > 0
|
|
517
|
+
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.`
|
|
518
|
+
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).`,
|
|
519
|
+
source: program.fileName,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// ── Hardware-timer instance validity ────────────────────────────────────
|
|
525
|
+
// hwtimer.* resolves the instance index to a counter device via the chip
|
|
526
|
+
// descriptor's hwtimer.controllers. A chip without that entry (or an
|
|
527
|
+
// out-of-range instance) lowers to a comment — flag it so the user knows
|
|
528
|
+
// the timer will never fire.
|
|
529
|
+
if (hwtimerInstances.size > 0) {
|
|
530
|
+
const controllerCount = chip.hwtimer?.controllers.length ?? 0;
|
|
531
|
+
for (const inst of hwtimerInstances) {
|
|
532
|
+
if (controllerCount === 0) {
|
|
533
|
+
diags.push({
|
|
534
|
+
severity: 'error',
|
|
535
|
+
code: 'zephyr-hwtimer-unavailable',
|
|
536
|
+
message: `Hardware timer instance ${inst} is used but ${chip.id} exposes no free counter device.`,
|
|
537
|
+
hint: `${chip.id} declares no hwtimer.controllers. Use a target with a free counter (e.g. nRF RTC1).`,
|
|
538
|
+
source: program.fileName,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else if (inst < 0 || inst >= controllerCount) {
|
|
542
|
+
diags.push({
|
|
543
|
+
severity: 'error',
|
|
544
|
+
code: 'zephyr-hwtimer-instance-out-of-range',
|
|
545
|
+
message: `Hardware timer instance ${inst} is out of range on ${chip.id} (0..${controllerCount - 1}).`,
|
|
546
|
+
source: program.fileName,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
467
551
|
// ── WiFi target validity ────────────────────────────────────────────────
|
|
468
552
|
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
469
553
|
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
@@ -592,6 +676,18 @@ export class ZephyrStrategy {
|
|
|
592
676
|
v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
593
677
|
v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
594
678
|
}
|
|
679
|
+
// String-method lowering is shared across targets (see string-method-
|
|
680
|
+
// registry). Zephyr's strings are const char*, so the __tc_* helpers this
|
|
681
|
+
// rewrites to (defined by the string_methods polyfill) take const char*.
|
|
682
|
+
// includes/startsWith lower to inline strstr/strncmp (matching framework-
|
|
683
|
+
// arduino); everything else → a __tc_* helper call.
|
|
684
|
+
v = applyStringMethodRewrites(v, {
|
|
685
|
+
wrapReceiverFor: new Set(['indexOf']),
|
|
686
|
+
special: {
|
|
687
|
+
includes: (recv, args) => `(strstr(${recv}, ${args[0]}) != NULL)`,
|
|
688
|
+
startsWith: (recv, args) => `(strncmp(${recv}, ${args[0]}, strlen(${args[0]})) == 0)`,
|
|
689
|
+
},
|
|
690
|
+
});
|
|
595
691
|
return v;
|
|
596
692
|
}
|
|
597
693
|
nullValue() {
|
|
@@ -840,15 +936,22 @@ export class ZephyrStrategy {
|
|
|
840
936
|
};
|
|
841
937
|
}
|
|
842
938
|
// ── Polyfills ───────────────────────────────────────────────────────────
|
|
843
|
-
//
|
|
844
|
-
//
|
|
845
|
-
//
|
|
939
|
+
// Zephyr is a no-STL target (hasVector/hasString = false), so array/string
|
|
940
|
+
// literals lower to __tc_StaticArray / const char* and string methods lower
|
|
941
|
+
// to __tc_* helpers — both need STL-free definitions emitted here (there is
|
|
942
|
+
// no shared-runtime fallback; the pipeline sources 100% of polyfills from
|
|
943
|
+
// generateNativePolyfills). Mirrors framework-arduino's AVR polyfills.
|
|
846
944
|
nativePolyfills() {
|
|
847
945
|
// cuttlefish_halt: always (the runtime header may reference it).
|
|
946
|
+
// string_methods / static_array: STL-free array + string helpers a no-STL
|
|
947
|
+
// target needs (mutated/struct array literals + any string method).
|
|
848
948
|
// timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
|
|
849
949
|
// timerCallCount at emit time in generateNativePolyfills).
|
|
850
950
|
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
851
|
-
return new Set([
|
|
951
|
+
return new Set([
|
|
952
|
+
'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
|
|
953
|
+
'timer_methods', 'async_runtime',
|
|
954
|
+
]);
|
|
852
955
|
}
|
|
853
956
|
generateNativePolyfills(program, ctx) {
|
|
854
957
|
const polyfills = [
|
|
@@ -897,6 +1000,75 @@ export class ZephyrStrategy {
|
|
|
897
1000
|
],
|
|
898
1001
|
dependencies: [],
|
|
899
1002
|
},
|
|
1003
|
+
{
|
|
1004
|
+
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
1005
|
+
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
1006
|
+
// every target; this supplies their definitions. Minimal-libc friendly:
|
|
1007
|
+
// only <cstring> primitives (no <cctype> — case conversion is inline
|
|
1008
|
+
// ASCII so the polyfill is self-contained). Mirrors framework-arduino.
|
|
1009
|
+
kind: 'polyfill',
|
|
1010
|
+
id: 'string_methods',
|
|
1011
|
+
domain: 'embedded',
|
|
1012
|
+
requiredIncludes: ['<cstring>'],
|
|
1013
|
+
forwardDeclarations: [],
|
|
1014
|
+
helperStructs: [],
|
|
1015
|
+
helperFunctions: [`
|
|
1016
|
+
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
1017
|
+
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
1018
|
+
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
1019
|
+
#endif
|
|
1020
|
+
bool __tc_endsWith(const char* s, const char* suffix) { int sl = strlen(s), tl = strlen(suffix); return sl >= tl && strcmp(s + sl - tl, suffix) == 0; }
|
|
1021
|
+
const char* __tc_toUpperCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'a' && *p <= 'z') { *p = static_cast<char>(*p - 32); } } return b; }
|
|
1022
|
+
const char* __tc_toLowerCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'A' && *p <= 'Z') { *p = static_cast<char>(*p + 32); } } return b; }
|
|
1023
|
+
const char* __tc_trim(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; while (*s == ' ' || *s == '\\t' || *s == '\\n' || *s == '\\r') s++; int len = strlen(s); while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\\t' || s[len-1] == '\\n' || s[len-1] == '\\r')) len--; int cplen = len < CUTTLEFISH_STR_BUF_SIZE - 1 ? len : CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s, cplen); b[cplen] = '\\0'; return b; }
|
|
1024
|
+
const char* __tc_substring2(const char* s, int start, int end) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; int slen = strlen(s); if (start < 0) start = 0; if (end > slen) end = slen; if (end < start) end = start; int len = end - start; if (len >= CUTTLEFISH_STR_BUF_SIZE) len = CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s + start, len); b[len] = '\\0'; return b; }
|
|
1025
|
+
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1026
|
+
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
1027
|
+
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1028
|
+
const char* __tc_replace(const char* s, const char* old, const char* repl) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; const char* pos = strstr(s, old); if (!pos) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } int beforeLen = static_cast<int>(pos - s); int oldLen = static_cast<int>(strlen(old)); int replLen = static_cast<int>(strlen(repl)); if (beforeLen + replLen + static_cast<int>(strlen(pos + oldLen)) >= CUTTLEFISH_STR_BUF_SIZE) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } memcpy(b, s, beforeLen); memcpy(b + beforeLen, repl, replLen); strcpy(b + beforeLen + replLen, pos + oldLen); return b; }
|
|
1029
|
+
const char* __tc_charAt(const char* s, int idx) { static char buf[2][2]; static uint8_t slot = 0; slot ^= 1; buf[slot][0] = s[idx]; buf[slot][1] = '\\0'; return buf[slot]; }
|
|
1030
|
+
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
1031
|
+
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
1032
|
+
`],
|
|
1033
|
+
shimMacros: [],
|
|
1034
|
+
dependencies: [],
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
// STL-free fixed-size array wrapper. Mutated/struct-element array
|
|
1038
|
+
// literals and array methods (.push/.pop/.map/.filter) lower to
|
|
1039
|
+
// __tc_StaticArray<T,N>; this supplies the template. Idempotent guard
|
|
1040
|
+
// so a redefinition is a no-op. Mirrors framework-arduino.
|
|
1041
|
+
kind: 'polyfill',
|
|
1042
|
+
id: 'static_array',
|
|
1043
|
+
domain: 'embedded',
|
|
1044
|
+
requiredIncludes: [],
|
|
1045
|
+
forwardDeclarations: [],
|
|
1046
|
+
helperStructs: [],
|
|
1047
|
+
helperFunctions: [`
|
|
1048
|
+
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
1049
|
+
#define __TC_STATIC_ARRAY_DEFINED
|
|
1050
|
+
template<typename T, int N>
|
|
1051
|
+
struct __tc_StaticArray {
|
|
1052
|
+
T data[N];
|
|
1053
|
+
int _size;
|
|
1054
|
+
__tc_StaticArray() : _size(0) {}
|
|
1055
|
+
int length() const { return _size; }
|
|
1056
|
+
int size() const { return _size; }
|
|
1057
|
+
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
1058
|
+
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
1059
|
+
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
1060
|
+
T& operator[](int i) { return data[i]; }
|
|
1061
|
+
const T& operator[](int i) const { return data[i]; }
|
|
1062
|
+
T* begin() { return &data[0]; }
|
|
1063
|
+
T* end() { return &data[_size]; }
|
|
1064
|
+
const T* begin() const { return &data[0]; }
|
|
1065
|
+
const T* end() const { return &data[_size]; }
|
|
1066
|
+
};
|
|
1067
|
+
#endif
|
|
1068
|
+
`],
|
|
1069
|
+
shimMacros: [],
|
|
1070
|
+
dependencies: [],
|
|
1071
|
+
},
|
|
900
1072
|
];
|
|
901
1073
|
// Worker-offload runtime (Phase 1). Emitted only when the program uses
|
|
902
1074
|
// worker.* ops, backed by the Zephyr primitives in worker-backing.ts
|
package/dist/toolchain/compat.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { readFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import manifest from '../framework.manifest.js';
|
|
18
|
+
import { discoverWest } from './west-discover.js';
|
|
18
19
|
// ── minimal semver ──────────────────────────────────────────────────────────
|
|
19
20
|
/** Parse "4.3.99" / "v4.3" / "4.3.99-rc1" → [4, 3, 99]. Undefined if unparseable. */
|
|
20
21
|
export function parseVersion(v) {
|
|
@@ -70,7 +71,15 @@ export function satisfiesRange(version, range) {
|
|
|
70
71
|
* and a bare "4.3.99".
|
|
71
72
|
*/
|
|
72
73
|
export function detectZephyrVersion() {
|
|
73
|
-
|
|
74
|
+
let base = process.env.ZEPHYR_BASE;
|
|
75
|
+
if (!base) {
|
|
76
|
+
// Fall back to the discovered west install's zephyrBase — covers the
|
|
77
|
+
// micromamba env from @typecad/zephyr-installer WITHOUT activation.
|
|
78
|
+
// (micromamba run sets ZEPHYR_BASE only inside the west subprocess; this
|
|
79
|
+
// makes the compat check work in the parent cuttlefish process too.)
|
|
80
|
+
const install = discoverWest();
|
|
81
|
+
base = install?.zephyrBase;
|
|
82
|
+
}
|
|
74
83
|
if (!base)
|
|
75
84
|
return undefined;
|
|
76
85
|
let content;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type WestInstall } from './west-discover.js';
|
|
2
|
+
import { type CompatStatus } from './compat.js';
|
|
3
|
+
/**
|
|
4
|
+
* Raw west facts gathered from discovery + a `west --version` probe. Mirrors
|
|
5
|
+
* ArduinoCliProbeData: `westFound` is true when a usable west install was
|
|
6
|
+
* discovered (discovery itself probes responsiveness).
|
|
7
|
+
*/
|
|
8
|
+
export interface WestProbeData {
|
|
9
|
+
/** A usable west install was discovered. */
|
|
10
|
+
westFound: boolean;
|
|
11
|
+
/** west version string if the `--version` probe parsed one, e.g. "1.3.0". */
|
|
12
|
+
westVersion: string | undefined;
|
|
13
|
+
/** Which discovery strategy found west, for surfacing to the user. */
|
|
14
|
+
source: WestInstall['source'] | undefined;
|
|
15
|
+
/** Effective ZEPHYR_BASE (env var, else a base discovery surfaced). */
|
|
16
|
+
zephyrBase: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Test-injection seam for checkZephyrEnv. Mirrors Arduino's
|
|
20
|
+
* CheckArduinoEnvOptions.fakeProbe so tests never spawn a real west/python.
|
|
21
|
+
*/
|
|
22
|
+
export interface CheckZephyrEnvOptions {
|
|
23
|
+
/** FOR TESTS ONLY: skip the real probe and use this data directly. */
|
|
24
|
+
fakeWestProbe?: WestProbeData;
|
|
25
|
+
/** FOR TESTS ONLY: override the board-existence lookup. */
|
|
26
|
+
fakeBoardExists?: (boardId: string, zephyrBase: string | undefined) => boolean | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface ZephyrEnvCheck {
|
|
29
|
+
/** west (the Zephyr build tool) was discovered and responsive. */
|
|
30
|
+
westFound: boolean;
|
|
31
|
+
/** west version string if known, e.g. "1.3.0". */
|
|
32
|
+
westVersion: string | undefined;
|
|
33
|
+
/** Discovery strategy that found west, for display. */
|
|
34
|
+
westSource: WestInstall['source'] | undefined;
|
|
35
|
+
/** Effective ZEPHYR_BASE (env var, else a discovered base). */
|
|
36
|
+
zephyrBase: string | undefined;
|
|
37
|
+
/** Detected Zephyr RTOS version from $ZEPHYR_BASE/VERSION, if readable. */
|
|
38
|
+
zephyrVersion: string | undefined;
|
|
39
|
+
/** Declared supported range (manifest.compat.zephyr), if any. */
|
|
40
|
+
compatRange: string | undefined;
|
|
41
|
+
/** Result of the compat-range check against the detected version. */
|
|
42
|
+
compatStatus: CompatStatus;
|
|
43
|
+
/** Raw board target from cuttlefish.config.ts, if configured. */
|
|
44
|
+
buildTarget: string | undefined;
|
|
45
|
+
/** buildTarget normalized for the installed Zephyr version (may equal it). */
|
|
46
|
+
resolvedBoardTarget: string | undefined;
|
|
47
|
+
/** Does the resolved board exist in the checkout? undefined = undetermined. */
|
|
48
|
+
boardTargetSupported: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
export type ZephyrEnvOk = {
|
|
51
|
+
ok: true;
|
|
52
|
+
check: ZephyrEnvCheck;
|
|
53
|
+
};
|
|
54
|
+
export type ZephyrEnvFailure = {
|
|
55
|
+
ok: false;
|
|
56
|
+
reason: 'west-not-found' | 'zephyr-out-of-range' | 'board-not-supported';
|
|
57
|
+
check: ZephyrEnvCheck;
|
|
58
|
+
/** Human-readable lines ready to print. */
|
|
59
|
+
messages: string[];
|
|
60
|
+
/** Exact remediation hint, when applicable. */
|
|
61
|
+
fixCommand: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
export type ZephyrEnvResult = ZephyrEnvOk | ZephyrEnvFailure;
|
|
64
|
+
/** Clear the west-probe cache (for tests). Also resets discovery cache. */
|
|
65
|
+
export declare function resetWestProbeCacheForTest(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Gather west facts: discover a usable install, then run `west --version`
|
|
68
|
+
* through it to capture the version. Memoized for the process lifetime (west
|
|
69
|
+
* installs don't move). Never throws — returns westFound:false on any failure.
|
|
70
|
+
*/
|
|
71
|
+
export declare function probeWestEnv(): WestProbeData;
|
|
72
|
+
/**
|
|
73
|
+
* Does `boardId` exist as a board directory in the Zephyr checkout? Checks the
|
|
74
|
+
* HWMv2 vendor layout used by Zephyr 4.x: $ZEPHYR_BASE/boards/<vendor>/<boardId>.
|
|
75
|
+
* Returns true/false when determinable; undefined when the base is unknown or
|
|
76
|
+
* the boards/ tree can't be read (so callers never fail on an inconclusive
|
|
77
|
+
* lookup — they just skip the board check).
|
|
78
|
+
*/
|
|
79
|
+
export declare function boardExistsInCheckout(boardId: string, zephyrBase: string | undefined): boolean | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Verify the environment can build for `buildTarget`. Cheap and
|
|
82
|
+
* side-effect-free: discovers west, reads the Zephyr version, checks the compat
|
|
83
|
+
* range, and — when a target is configured — verifies the board exists in the
|
|
84
|
+
* checkout. Reports what (if anything) is wrong.
|
|
85
|
+
*
|
|
86
|
+
* - If `buildTarget` is undefined/empty, the board check is skipped (not a
|
|
87
|
+
* failure), mirroring Arduino's no-FQBN path.
|
|
88
|
+
* - Never installs anything. Never mutates the user environment.
|
|
89
|
+
* - Never throws — always returns a result. Callers decide how to react.
|
|
90
|
+
*
|
|
91
|
+
* `options` is for-test only (injects fake probe data / board lookup).
|
|
92
|
+
*/
|
|
93
|
+
export declare function checkZephyrEnv(buildTarget?: string, options?: CheckZephyrEnvOptions): ZephyrEnvResult;
|