@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.
- 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.d.ts +25 -0
- package/dist/display/profiles.js +30 -0
- package/dist/display/touch-adapter.d.ts +3 -4
- package/dist/display/touch-adapter.js +119 -16
- package/dist/display/ui-adapter.js +308 -139
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +8 -1
- package/dist/dt-config/kconfig.js +40 -5
- package/dist/dt-config/overlay.d.ts +18 -1
- package/dist/dt-config/overlay.js +124 -26
- package/dist/framework.manifest.d.ts +29 -28
- package/dist/framework.manifest.js +46 -17
- 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/ble.js +3 -1
- 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.d.ts +4 -0
- package/dist/strategy.js +275 -35
- 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/index.js +39 -9
- package/dist/toolchain/scaffold.js +7 -2
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +84 -7
- 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 +53 -0
- package/src/display/touch-adapter.ts +119 -15
- package/src/display/ui-adapter.ts +311 -139
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +45 -6
- package/src/dt-config/overlay.ts +159 -29
- package/src/framework.manifest.ts +47 -17
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/ble.ts +3 -1
- 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 +271 -35
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/index.ts +40 -9
- package/src/toolchain/scaffold.ts +7 -2
- package/src/toolchain/west-discover.ts +92 -9
- package/src/toolchain/west-spawn.ts +15 -0
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'))
|
|
@@ -135,10 +150,11 @@ export class ZephyrStrategy {
|
|
|
135
150
|
// program-analysis usesStdString detector doesn't see types generated by
|
|
136
151
|
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
137
152
|
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
138
|
-
// type').
|
|
139
|
-
//
|
|
153
|
+
// type'). <cstdlib>/<cstring> (not <stdlib.h>/<string.h>) back the shim's
|
|
154
|
+
// strtol/strcmp/strncpy — the same AUTOSAR-compliant spelling the HTTP,
|
|
155
|
+
// MQTT, and Preferences paths below already use.
|
|
140
156
|
if (uses('usesBle'))
|
|
141
|
-
inc.push('<
|
|
157
|
+
inc.push('<cstdlib>', '<cstring>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
142
158
|
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
143
159
|
// this include. When ctx.analysis is absent (capability query), uses()
|
|
144
160
|
// defaults to true so a real build never strips it.
|
|
@@ -290,22 +306,28 @@ export class ZephyrStrategy {
|
|
|
290
306
|
lines.push(...adcInitLines(chip));
|
|
291
307
|
if (uses('usesPWM') && chip.pwm)
|
|
292
308
|
lines.push(...pwmInitLines(chip));
|
|
309
|
+
if (uses('usesDAC') && chip.dac)
|
|
310
|
+
lines.push(...dacInitLines(chip));
|
|
311
|
+
if (uses('usesHwtimer') && chip.hwtimer)
|
|
312
|
+
lines.push(...hwtimerInitLines(chip));
|
|
293
313
|
if (uses('usesInterrupts'))
|
|
294
314
|
lines.push(...interruptInitLines(chip));
|
|
295
315
|
if (uses('usesWDT') && chip.wdt)
|
|
296
316
|
lines.push(...wdtInitLines(chip));
|
|
297
317
|
if (uses('usesBle'))
|
|
298
318
|
lines.push(...bleInitLines());
|
|
299
|
-
// Display runtime (
|
|
300
|
-
//
|
|
301
|
-
// display
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
319
|
+
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
320
|
+
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
321
|
+
// when the program uses display.* but is NOT a UI program — the UI display
|
|
322
|
+
// adapter (emitted by cuttlefish's emitUIRuntime, solely under entryHasUI())
|
|
323
|
+
// defines the same display_init symbol, so emitting both would collide.
|
|
324
|
+
// `providesDisplayAdapter()` is a static capability (always true here) and
|
|
325
|
+
// does NOT track whether the adapter is actually emitted for THIS build, so
|
|
326
|
+
// the per-program UI signal (entryHasUI) is the correct gate. Without this,
|
|
327
|
+
// a direct display.* program has no definition for display_init/
|
|
328
|
+
// display_fill_rect/draw_rect/draw_text/flush (the gfx runtime was
|
|
329
|
+
// previously dead code).
|
|
330
|
+
if (uses('usesDisplay') && !entryHasUI()) {
|
|
309
331
|
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
310
332
|
lines.push(...rt.stateLines);
|
|
311
333
|
lines.push(rt.fontTable);
|
|
@@ -319,6 +341,8 @@ export class ZephyrStrategy {
|
|
|
319
341
|
lines.push(...mqttInitLines());
|
|
320
342
|
if (uses('usesPreferences'))
|
|
321
343
|
lines.push(...preferencesInitLines());
|
|
344
|
+
if (uses('usesFS'))
|
|
345
|
+
lines.push(...fsInitLines());
|
|
322
346
|
if (uses('usesRandom'))
|
|
323
347
|
lines.push(...randomInitLines());
|
|
324
348
|
lines.push('#endif // CUTTLEFISH_SHIM_DEFINED');
|
|
@@ -354,20 +378,27 @@ export class ZephyrStrategy {
|
|
|
354
378
|
// separate translation unit when generateHeaderFile() splits them into the
|
|
355
379
|
// header.
|
|
356
380
|
lines.push('extern void setup(void);', 'extern void loop(void);', '', 'int main(void) {', ' setup();', ' for (;;) {', ' loop();', ' k_msleep(1);', ' }', ' return 0;', '}');
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
381
|
+
// GPIO read shim: the wiring_compat polyfill routes the UI runtime
|
|
382
|
+
// header's unconditional digitalRead() poll (init-press-input.ts) to
|
|
383
|
+
// __tc_gpio_read, so the definition must NOT be gated on @typecad/safety.
|
|
384
|
+
// The signature is `int` to match wiring_compat's forward declaration —
|
|
385
|
+
// a uint32_t definition alongside it would leave the declared int
|
|
386
|
+
// overload undefined (int wins overload resolution for small integer
|
|
387
|
+
// arguments).
|
|
360
388
|
//
|
|
361
|
-
// The pin is a RUNTIME value here (
|
|
362
|
-
//
|
|
363
|
-
// on a multi-controller SoC (ESP32-S3:
|
|
364
|
-
// Emit a tiny __tc_gpio_dev(pin)
|
|
365
|
-
// controller's device per pin;
|
|
366
|
-
// one-liner. Each DT_NODELABEL is
|
|
367
|
-
// it is always statically valid.
|
|
389
|
+
// The pin is a RUNTIME value here (the UI pin-watch table and safety's
|
|
390
|
+
// voter pass whatever pin they were handed), so the controller cannot be
|
|
391
|
+
// baked in as a single DT_NODELABEL on a multi-controller SoC (ESP32-S3:
|
|
392
|
+
// pins 0–31 → gpio0, 32–48 → gpio1). Emit a tiny __tc_gpio_dev(pin)
|
|
393
|
+
// dispatcher that resolves the owning controller's device per pin;
|
|
394
|
+
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
395
|
+
// still compile-time-resolved per branch, so it is always statically valid.
|
|
396
|
+
lines.push(...emitGpioDevDispatcher(chip));
|
|
397
|
+
lines.push('inline int __tc_gpio_read(int pin) { return gpio_pin_get_raw(__tc_gpio_dev(static_cast<uint32_t>(pin)), static_cast<gpio_pin_t>(pin)); }');
|
|
398
|
+
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
399
|
+
// lowering, so they stay gated on it.
|
|
368
400
|
if (program && programUsesSafety(program)) {
|
|
369
|
-
lines.push(
|
|
370
|
-
lines.push('inline int __tc_gpio_read(uint32_t pin) { return gpio_pin_get_raw(__tc_gpio_dev(pin), pin); }', 'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }', '#ifndef __TC_DELAY_US_DEFINED', '#define __TC_DELAY_US_DEFINED', 'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }', '#endif');
|
|
401
|
+
lines.push('inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }', '#ifndef __TC_DELAY_US_DEFINED', '#define __TC_DELAY_US_DEFINED', 'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }', '#endif');
|
|
371
402
|
}
|
|
372
403
|
return lines;
|
|
373
404
|
}
|
|
@@ -384,6 +415,8 @@ export class ZephyrStrategy {
|
|
|
384
415
|
const outputPins = new Set();
|
|
385
416
|
const adcReadPins = new Set();
|
|
386
417
|
const interruptPins = new Set();
|
|
418
|
+
const dacPins = new Set();
|
|
419
|
+
const hwtimerInstances = new Set();
|
|
387
420
|
let usesWifiOps = false;
|
|
388
421
|
let usesHttpOps = false;
|
|
389
422
|
let usesMqttOps = false;
|
|
@@ -404,6 +437,16 @@ export class ZephyrStrategy {
|
|
|
404
437
|
if (op.operation === 'interrupt.attach' && typeof op.pin === 'number') {
|
|
405
438
|
interruptPins.add(op.pin);
|
|
406
439
|
}
|
|
440
|
+
if (op.operation === 'dac.write' && typeof op.pin === 'number') {
|
|
441
|
+
dacPins.add(op.pin);
|
|
442
|
+
}
|
|
443
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
|
|
444
|
+
const inst = typeof op.instance === 'number'
|
|
445
|
+
? op.instance
|
|
446
|
+
: parseInt(String(op.instance), 10);
|
|
447
|
+
if (!isNaN(inst))
|
|
448
|
+
hwtimerInstances.add(inst);
|
|
449
|
+
}
|
|
407
450
|
if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
|
|
408
451
|
usesWifiOps = true;
|
|
409
452
|
}
|
|
@@ -464,6 +507,55 @@ export class ZephyrStrategy {
|
|
|
464
507
|
});
|
|
465
508
|
}
|
|
466
509
|
}
|
|
510
|
+
// ── DAC pin validity ────────────────────────────────────────────────────
|
|
511
|
+
// dac.write resolves a HAL pin to a channel via the chip descriptor's
|
|
512
|
+
// dac.channels map. A pin not in that map lowers to a comment (silent
|
|
513
|
+
// no-op), and a chip without a `dac` entry (nRF52840, ESP32-S3) has no DAC
|
|
514
|
+
// at all. Flag either case so the user gets a clear message instead of a
|
|
515
|
+
// pin that silently does nothing.
|
|
516
|
+
if (dacPins.size > 0) {
|
|
517
|
+
const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
|
|
518
|
+
for (const pin of dacPins) {
|
|
519
|
+
if (!dacChannels.has(pin)) {
|
|
520
|
+
diags.push({
|
|
521
|
+
severity: 'error',
|
|
522
|
+
code: 'zephyr-dac-pin-unavailable',
|
|
523
|
+
message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
|
|
524
|
+
hint: dacChannels.size > 0
|
|
525
|
+
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.`
|
|
526
|
+
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).`,
|
|
527
|
+
source: program.fileName,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// ── Hardware-timer instance validity ────────────────────────────────────
|
|
533
|
+
// hwtimer.* resolves the instance index to a counter device via the chip
|
|
534
|
+
// descriptor's hwtimer.controllers. A chip without that entry (or an
|
|
535
|
+
// out-of-range instance) lowers to a comment — flag it so the user knows
|
|
536
|
+
// the timer will never fire.
|
|
537
|
+
if (hwtimerInstances.size > 0) {
|
|
538
|
+
const controllerCount = chip.hwtimer?.controllers.length ?? 0;
|
|
539
|
+
for (const inst of hwtimerInstances) {
|
|
540
|
+
if (controllerCount === 0) {
|
|
541
|
+
diags.push({
|
|
542
|
+
severity: 'error',
|
|
543
|
+
code: 'zephyr-hwtimer-unavailable',
|
|
544
|
+
message: `Hardware timer instance ${inst} is used but ${chip.id} exposes no free counter device.`,
|
|
545
|
+
hint: `${chip.id} declares no hwtimer.controllers. Use a target with a free counter (e.g. nRF RTC1).`,
|
|
546
|
+
source: program.fileName,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
else if (inst < 0 || inst >= controllerCount) {
|
|
550
|
+
diags.push({
|
|
551
|
+
severity: 'error',
|
|
552
|
+
code: 'zephyr-hwtimer-instance-out-of-range',
|
|
553
|
+
message: `Hardware timer instance ${inst} is out of range on ${chip.id} (0..${controllerCount - 1}).`,
|
|
554
|
+
source: program.fileName,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
467
559
|
// ── WiFi target validity ────────────────────────────────────────────────
|
|
468
560
|
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
469
561
|
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
@@ -592,6 +684,18 @@ export class ZephyrStrategy {
|
|
|
592
684
|
v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
593
685
|
v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
594
686
|
}
|
|
687
|
+
// String-method lowering is shared across targets (see string-method-
|
|
688
|
+
// registry). Zephyr's strings are const char*, so the __tc_* helpers this
|
|
689
|
+
// rewrites to (defined by the string_methods polyfill) take const char*.
|
|
690
|
+
// includes/startsWith lower to inline strstr/strncmp (matching framework-
|
|
691
|
+
// arduino); everything else → a __tc_* helper call.
|
|
692
|
+
v = applyStringMethodRewrites(v, {
|
|
693
|
+
wrapReceiverFor: new Set(['indexOf']),
|
|
694
|
+
special: {
|
|
695
|
+
includes: (recv, args) => `(strstr(${recv}, ${args[0]}) != NULL)`,
|
|
696
|
+
startsWith: (recv, args) => `(strncmp(${recv}, ${args[0]}, strlen(${args[0]})) == 0)`,
|
|
697
|
+
},
|
|
698
|
+
});
|
|
595
699
|
return v;
|
|
596
700
|
}
|
|
597
701
|
nullValue() {
|
|
@@ -684,6 +788,61 @@ export class ZephyrStrategy {
|
|
|
684
788
|
apiReservedEnumGuard() {
|
|
685
789
|
return '';
|
|
686
790
|
}
|
|
791
|
+
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
792
|
+
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
793
|
+
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
794
|
+
// debug builds; corrupts scheduler state otherwise). The names below are the
|
|
795
|
+
// IR-level callees cuttlefish's interrupt-analysis pass matches (the same
|
|
796
|
+
// keys ArduinoStrategy uses; timing.delay/delay_microseconds hal-ops are
|
|
797
|
+
// mapped back to the bare names by the analyzer itself).
|
|
798
|
+
isrUnsafeOperations() {
|
|
799
|
+
return new Map([
|
|
800
|
+
['delay', {
|
|
801
|
+
reason: 'delay() lowers to k_msleep(), which sleeps the calling thread — illegal in Zephyr interrupt context (submit a k_work item or arm a k_timer instead)',
|
|
802
|
+
severity: 'warning',
|
|
803
|
+
}],
|
|
804
|
+
['delayMicroseconds', {
|
|
805
|
+
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
806
|
+
severity: 'warning',
|
|
807
|
+
}],
|
|
808
|
+
['console.log', {
|
|
809
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
810
|
+
severity: 'info',
|
|
811
|
+
}],
|
|
812
|
+
['console.error', {
|
|
813
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
814
|
+
severity: 'info',
|
|
815
|
+
}],
|
|
816
|
+
['console.warn', {
|
|
817
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
818
|
+
severity: 'info',
|
|
819
|
+
}],
|
|
820
|
+
['I2C0', {
|
|
821
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
822
|
+
severity: 'warning',
|
|
823
|
+
}],
|
|
824
|
+
['I2C1', {
|
|
825
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
826
|
+
severity: 'warning',
|
|
827
|
+
}],
|
|
828
|
+
['SPI0', {
|
|
829
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
830
|
+
severity: 'warning',
|
|
831
|
+
}],
|
|
832
|
+
['SPI1', {
|
|
833
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
834
|
+
severity: 'warning',
|
|
835
|
+
}],
|
|
836
|
+
['UART0', {
|
|
837
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
838
|
+
severity: 'info',
|
|
839
|
+
}],
|
|
840
|
+
['UART1', {
|
|
841
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
842
|
+
severity: 'info',
|
|
843
|
+
}],
|
|
844
|
+
]);
|
|
845
|
+
}
|
|
687
846
|
ambientTypeDeclarations() {
|
|
688
847
|
// Preferences is the only HAL surface the framework lowers that is used as
|
|
689
848
|
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
@@ -840,15 +999,22 @@ export class ZephyrStrategy {
|
|
|
840
999
|
};
|
|
841
1000
|
}
|
|
842
1001
|
// ── Polyfills ───────────────────────────────────────────────────────────
|
|
843
|
-
//
|
|
844
|
-
//
|
|
845
|
-
//
|
|
1002
|
+
// Zephyr is a no-STL target (hasVector/hasString = false), so array/string
|
|
1003
|
+
// literals lower to __tc_StaticArray / const char* and string methods lower
|
|
1004
|
+
// to __tc_* helpers — both need STL-free definitions emitted here (there is
|
|
1005
|
+
// no shared-runtime fallback; the pipeline sources 100% of polyfills from
|
|
1006
|
+
// generateNativePolyfills). Mirrors framework-arduino's AVR polyfills.
|
|
846
1007
|
nativePolyfills() {
|
|
847
1008
|
// cuttlefish_halt: always (the runtime header may reference it).
|
|
1009
|
+
// string_methods / static_array: STL-free array + string helpers a no-STL
|
|
1010
|
+
// target needs (mutated/struct array literals + any string method).
|
|
848
1011
|
// timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
|
|
849
1012
|
// timerCallCount at emit time in generateNativePolyfills).
|
|
850
1013
|
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
851
|
-
return new Set([
|
|
1014
|
+
return new Set([
|
|
1015
|
+
'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
|
|
1016
|
+
'timer_methods', 'async_runtime',
|
|
1017
|
+
]);
|
|
852
1018
|
}
|
|
853
1019
|
generateNativePolyfills(program, ctx) {
|
|
854
1020
|
const polyfills = [
|
|
@@ -897,6 +1063,75 @@ export class ZephyrStrategy {
|
|
|
897
1063
|
],
|
|
898
1064
|
dependencies: [],
|
|
899
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
1068
|
+
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
1069
|
+
// every target; this supplies their definitions. Minimal-libc friendly:
|
|
1070
|
+
// only <cstring> primitives (no <cctype> — case conversion is inline
|
|
1071
|
+
// ASCII so the polyfill is self-contained). Mirrors framework-arduino.
|
|
1072
|
+
kind: 'polyfill',
|
|
1073
|
+
id: 'string_methods',
|
|
1074
|
+
domain: 'embedded',
|
|
1075
|
+
requiredIncludes: ['<cstring>'],
|
|
1076
|
+
forwardDeclarations: [],
|
|
1077
|
+
helperStructs: [],
|
|
1078
|
+
helperFunctions: [`
|
|
1079
|
+
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
1080
|
+
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
1081
|
+
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
1082
|
+
#endif
|
|
1083
|
+
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; }
|
|
1084
|
+
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; }
|
|
1085
|
+
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; }
|
|
1086
|
+
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; }
|
|
1087
|
+
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; }
|
|
1088
|
+
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1089
|
+
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
1090
|
+
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1091
|
+
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; }
|
|
1092
|
+
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]; }
|
|
1093
|
+
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
1094
|
+
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
1095
|
+
`],
|
|
1096
|
+
shimMacros: [],
|
|
1097
|
+
dependencies: [],
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
// STL-free fixed-size array wrapper. Mutated/struct-element array
|
|
1101
|
+
// literals and array methods (.push/.pop/.map/.filter) lower to
|
|
1102
|
+
// __tc_StaticArray<T,N>; this supplies the template. Idempotent guard
|
|
1103
|
+
// so a redefinition is a no-op. Mirrors framework-arduino.
|
|
1104
|
+
kind: 'polyfill',
|
|
1105
|
+
id: 'static_array',
|
|
1106
|
+
domain: 'embedded',
|
|
1107
|
+
requiredIncludes: [],
|
|
1108
|
+
forwardDeclarations: [],
|
|
1109
|
+
helperStructs: [],
|
|
1110
|
+
helperFunctions: [`
|
|
1111
|
+
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
1112
|
+
#define __TC_STATIC_ARRAY_DEFINED
|
|
1113
|
+
template<typename T, int N>
|
|
1114
|
+
struct __tc_StaticArray {
|
|
1115
|
+
T data[N];
|
|
1116
|
+
int _size;
|
|
1117
|
+
__tc_StaticArray() : _size(0) {}
|
|
1118
|
+
int length() const { return _size; }
|
|
1119
|
+
int size() const { return _size; }
|
|
1120
|
+
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
1121
|
+
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
1122
|
+
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
1123
|
+
T& operator[](int i) { return data[i]; }
|
|
1124
|
+
const T& operator[](int i) const { return data[i]; }
|
|
1125
|
+
T* begin() { return &data[0]; }
|
|
1126
|
+
T* end() { return &data[_size]; }
|
|
1127
|
+
const T* begin() const { return &data[0]; }
|
|
1128
|
+
const T* end() const { return &data[_size]; }
|
|
1129
|
+
};
|
|
1130
|
+
#endif
|
|
1131
|
+
`],
|
|
1132
|
+
shimMacros: [],
|
|
1133
|
+
dependencies: [],
|
|
1134
|
+
},
|
|
900
1135
|
];
|
|
901
1136
|
// Worker-offload runtime (Phase 1). Emitted only when the program uses
|
|
902
1137
|
// worker.* ops, backed by the Zephyr primitives in worker-backing.ts
|
|
@@ -926,7 +1161,11 @@ export class ZephyrStrategy {
|
|
|
926
1161
|
id: 'async_runtime',
|
|
927
1162
|
domain: 'embedded',
|
|
928
1163
|
requiredIncludes: [],
|
|
929
|
-
|
|
1164
|
+
// Polyfill definitions emit before shimLines, but the runtime's
|
|
1165
|
+
// timer bodies call millis() (defined in shimLines) — declare it
|
|
1166
|
+
// first so the polyfill compiles even for programs whose source
|
|
1167
|
+
// has no explicit timing call.
|
|
1168
|
+
forwardDeclarations: ['unsigned long millis();'],
|
|
930
1169
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
931
1170
|
helperFunctions: [],
|
|
932
1171
|
shimMacros: [],
|
|
@@ -1012,11 +1251,12 @@ export class ZephyrStrategy {
|
|
|
1012
1251
|
}
|
|
1013
1252
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1014
1253
|
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
1015
|
-
// the in-tree CuttlefishGFX class to
|
|
1016
|
-
// src/display/ui-adapter.ts), and the
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
//
|
|
1254
|
+
// the in-tree CuttlefishGFX class to the panel (per-controller init + wire
|
|
1255
|
+
// format, see src/display/ui-adapter.ts), and the touch adapters drive the
|
|
1256
|
+
// FT6336U (I2C capacitive) and XPT2046 (SPI resistive) controllers via
|
|
1257
|
+
// Zephyr's bus APIs (src/display/touch-adapter.ts). Both live in this
|
|
1258
|
+
// package so cuttlefish carries no Zephyr/Wiring-specific display or touch
|
|
1259
|
+
// knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
1020
1260
|
providesDisplayAdapter() { return true; }
|
|
1021
1261
|
resolveDisplayAdapter(display) {
|
|
1022
1262
|
const code = zephyrDisplayAdapterGenerator(display);
|
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;
|