@typecad/framework-zephyr 1.0.0-alpha.8

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/async/timer-polyfill.d.ts +10 -0
  4. package/dist/async/timer-polyfill.js +95 -0
  5. package/dist/chips/controllers.d.ts +21 -0
  6. package/dist/chips/controllers.js +66 -0
  7. package/dist/chips/esp32.d.ts +2 -0
  8. package/dist/chips/esp32.js +59 -0
  9. package/dist/chips/esp32s3.d.ts +2 -0
  10. package/dist/chips/esp32s3.js +54 -0
  11. package/dist/chips/index.d.ts +15 -0
  12. package/dist/chips/index.js +45 -0
  13. package/dist/chips/resolve.d.ts +9 -0
  14. package/dist/chips/resolve.js +121 -0
  15. package/dist/chips/types.d.ts +154 -0
  16. package/dist/chips/types.js +14 -0
  17. package/dist/chips/xiao-ble.d.ts +2 -0
  18. package/dist/chips/xiao-ble.js +61 -0
  19. package/dist/debug-codegen.d.ts +33 -0
  20. package/dist/debug-codegen.js +162 -0
  21. package/dist/display/gfx.d.ts +17 -0
  22. package/dist/display/gfx.js +171 -0
  23. package/dist/display/index.d.ts +20 -0
  24. package/dist/display/index.js +53 -0
  25. package/dist/display/profiles.d.ts +25 -0
  26. package/dist/display/profiles.js +41 -0
  27. package/dist/display/touch-adapter.d.ts +9 -0
  28. package/dist/display/touch-adapter.js +95 -0
  29. package/dist/display/ui-adapter.d.ts +21 -0
  30. package/dist/display/ui-adapter.js +534 -0
  31. package/dist/dt-config/kconfig.d.ts +27 -0
  32. package/dist/dt-config/kconfig.js +283 -0
  33. package/dist/dt-config/overlay.d.ts +35 -0
  34. package/dist/dt-config/overlay.js +246 -0
  35. package/dist/framework.manifest.d.ts +98 -0
  36. package/dist/framework.manifest.js +525 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.js +12 -0
  39. package/dist/lowering/adc.d.ts +19 -0
  40. package/dist/lowering/adc.js +76 -0
  41. package/dist/lowering/ble.d.ts +20 -0
  42. package/dist/lowering/ble.js +419 -0
  43. package/dist/lowering/board.d.ts +9 -0
  44. package/dist/lowering/board.js +33 -0
  45. package/dist/lowering/gpio.d.ts +12 -0
  46. package/dist/lowering/gpio.js +123 -0
  47. package/dist/lowering/http.d.ts +23 -0
  48. package/dist/lowering/http.js +482 -0
  49. package/dist/lowering/i2c.d.ts +15 -0
  50. package/dist/lowering/i2c.js +114 -0
  51. package/dist/lowering/index.d.ts +29 -0
  52. package/dist/lowering/index.js +93 -0
  53. package/dist/lowering/interrupts.d.ts +21 -0
  54. package/dist/lowering/interrupts.js +91 -0
  55. package/dist/lowering/mqtt.d.ts +19 -0
  56. package/dist/lowering/mqtt.js +366 -0
  57. package/dist/lowering/power.d.ts +9 -0
  58. package/dist/lowering/power.js +64 -0
  59. package/dist/lowering/preferences.d.ts +23 -0
  60. package/dist/lowering/preferences.js +345 -0
  61. package/dist/lowering/pulse.d.ts +7 -0
  62. package/dist/lowering/pulse.js +82 -0
  63. package/dist/lowering/pwm.d.ts +15 -0
  64. package/dist/lowering/pwm.js +59 -0
  65. package/dist/lowering/random.d.ts +18 -0
  66. package/dist/lowering/random.js +129 -0
  67. package/dist/lowering/spi.d.ts +17 -0
  68. package/dist/lowering/spi.js +113 -0
  69. package/dist/lowering/timing.d.ts +9 -0
  70. package/dist/lowering/timing.js +63 -0
  71. package/dist/lowering/tone.d.ts +10 -0
  72. package/dist/lowering/tone.js +50 -0
  73. package/dist/lowering/uart.d.ts +15 -0
  74. package/dist/lowering/uart.js +102 -0
  75. package/dist/lowering/util.d.ts +4 -0
  76. package/dist/lowering/util.js +12 -0
  77. package/dist/lowering/wdt.d.ts +15 -0
  78. package/dist/lowering/wdt.js +80 -0
  79. package/dist/lowering/wifi.d.ts +21 -0
  80. package/dist/lowering/wifi.js +447 -0
  81. package/dist/lowering/worker-backing.d.ts +14 -0
  82. package/dist/lowering/worker-backing.js +79 -0
  83. package/dist/lowering/worker.d.ts +6 -0
  84. package/dist/lowering/worker.js +14 -0
  85. package/dist/strategy.d.ts +168 -0
  86. package/dist/strategy.js +1094 -0
  87. package/dist/toolchain/debug-config.d.ts +82 -0
  88. package/dist/toolchain/debug-config.js +359 -0
  89. package/dist/toolchain/index.d.ts +73 -0
  90. package/dist/toolchain/index.js +427 -0
  91. package/dist/toolchain/scaffold.d.ts +15 -0
  92. package/dist/toolchain/scaffold.js +196 -0
  93. package/dist/toolchain/west-discover.d.ts +40 -0
  94. package/dist/toolchain/west-discover.js +203 -0
  95. package/dist/toolchain/west-spawn.d.ts +39 -0
  96. package/dist/toolchain/west-spawn.js +117 -0
  97. package/package.json +82 -0
  98. package/src/async/timer-polyfill.ts +107 -0
  99. package/src/chips/controllers.ts +74 -0
  100. package/src/chips/esp32.ts +62 -0
  101. package/src/chips/esp32s3.ts +57 -0
  102. package/src/chips/index.ts +54 -0
  103. package/src/chips/resolve.ts +146 -0
  104. package/src/chips/types.ts +163 -0
  105. package/src/chips/xiao-ble.ts +64 -0
  106. package/src/debug-codegen.ts +207 -0
  107. package/src/display/gfx.ts +190 -0
  108. package/src/display/index.ts +70 -0
  109. package/src/display/profiles.ts +63 -0
  110. package/src/display/touch-adapter.ts +100 -0
  111. package/src/display/ui-adapter.ts +558 -0
  112. package/src/dt-config/kconfig.ts +308 -0
  113. package/src/dt-config/overlay.ts +293 -0
  114. package/src/framework.manifest.ts +545 -0
  115. package/src/index.ts +23 -0
  116. package/src/lowering/adc.ts +102 -0
  117. package/src/lowering/ble.ts +426 -0
  118. package/src/lowering/board.ts +38 -0
  119. package/src/lowering/gpio.ts +148 -0
  120. package/src/lowering/http.ts +488 -0
  121. package/src/lowering/i2c.ts +126 -0
  122. package/src/lowering/index.ts +86 -0
  123. package/src/lowering/interrupts.ts +114 -0
  124. package/src/lowering/mqtt.ts +371 -0
  125. package/src/lowering/power.ts +70 -0
  126. package/src/lowering/preferences.ts +354 -0
  127. package/src/lowering/pulse.ts +93 -0
  128. package/src/lowering/pwm.ts +74 -0
  129. package/src/lowering/random.ts +135 -0
  130. package/src/lowering/spi.ts +124 -0
  131. package/src/lowering/timing.ts +72 -0
  132. package/src/lowering/tone.ts +60 -0
  133. package/src/lowering/uart.ts +111 -0
  134. package/src/lowering/util.ts +12 -0
  135. package/src/lowering/wdt.ts +84 -0
  136. package/src/lowering/wifi.ts +452 -0
  137. package/src/lowering/worker-backing.ts +94 -0
  138. package/src/lowering/worker.ts +17 -0
  139. package/src/strategy.ts +1265 -0
  140. package/src/toolchain/debug-config.ts +399 -0
  141. package/src/toolchain/index.ts +459 -0
  142. package/src/toolchain/scaffold.ts +196 -0
  143. package/src/toolchain/west-discover.ts +233 -0
  144. package/src/toolchain/west-spawn.ts +141 -0
@@ -0,0 +1,129 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Random lowering — Zephyr random subsystem (sys_rand_get) + xorshift32 PRNG
3
+ //
4
+ // Zephyr's <zephyr/random/random.h> exposes sys_rand_get(dst, len), backed by
5
+ // the configured entropy source (the nRF52840 hardware RNG when
6
+ // CONFIG_HARDWARE_DEVICE_RANDOM_GENERATOR is selected, or the default test/
7
+ // xoroshiro generator otherwise). It is seeded automatically at boot from the
8
+ // entropy driver, so unlike Arduino there is no implicit seeding step.
9
+ //
10
+ // The HAL random surface is the Arduino-core model:
11
+ // random.int() → a non-negative 31-bit integer [0, 2^31 - 1]
12
+ // random.range(a,b) → [a, b-1] (Random.upTo(n) collapses to range(0, n))
13
+ // random.seed(s) → re-seed the PRNG (Arduino randomSeed makes the sequence
14
+ // deterministic from s)
15
+ //
16
+ // To honor random.seed (which sys_rand_get cannot, being a non-seedable
17
+ // entropy tap), the lowering keeps a userspace xorshift32 PRNG. It is seeded
18
+ // once from sys_rand_get on first use (so an unseeded program gets
19
+ // hardware-random behavior), and re-seeded verbatim by random.seed (so a
20
+ // program that calls Random.seed(42) gets a deterministic sequence, matching
21
+ // the Arduino contract). xorshift32 is a 2^32-1-period PRNG adequate for the
22
+ // non-cryptographic random.* surface; sys_csrand_get exists for crypto use the
23
+ // HAL does not expose.
24
+ //
25
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
26
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
27
+ // ---------------------------------------------------------------------------
28
+ /**
29
+ * The random runtime shim. The xorshift32 state is a 32-bit word seeded from
30
+ * sys_rand_get on first use; random.seed overwrites it. Helpers are
31
+ * `static`/`inline` so the single generated TU does not trip -Wunused-function
32
+ * when only a subset of the ops is used (mirrors preferences.ts / wifi.ts).
33
+ */
34
+ export function randomInitLines() {
35
+ return [
36
+ `// CUTTLEFISH_RANDOM_BEGIN`,
37
+ `#include <zephyr/random/random.h>`,
38
+ ``,
39
+ `// xorshift32 state. Seeded from sys_rand_get on first use; random.seed`,
40
+ `// overwrites it. uint32_t (not int32_t) so the sign bit never leaks into`,
41
+ `// the HAL's non-negative random.int range.`,
42
+ `static uint32_t __tc_rand_state = 0U;`,
43
+ `static bool __tc_rand_seeded = false;`,
44
+ ``,
45
+ `// One-time seed from the Zephyr entropy tap. Called lazily so a program`,
46
+ `// that only ever calls Random.seed never touches sys_rand_get (and so a`,
47
+ `// capability probe with no board never needs the entropy driver at all).`,
48
+ `static inline void __tc_rand_ensure_seeded(void) {`,
49
+ ` if (!__tc_rand_seeded) {`,
50
+ ` uint32_t seed = 0U;`,
51
+ ` (void)sys_rand_get(&seed, sizeof(seed));`,
52
+ ` // xorshift32 needs a non-zero state; if the entropy tap returns 0`,
53
+ ` // (vanishingly unlikely), fall back to a fixed odd constant so the`,
54
+ ` // sequence is still well-defined rather than stuck at 0.`,
55
+ ` if (seed == 0U) { seed = 0x9E3779B9U; }`,
56
+ ` __tc_rand_state = seed;`,
57
+ ` __tc_rand_seeded = true;`,
58
+ ` }`,
59
+ `}`,
60
+ ``,
61
+ `// Advance the xorshift32 state one step and return the raw 32-bit word.`,
62
+ `static inline uint32_t __tc_rand_next(void) {`,
63
+ ` __tc_rand_ensure_seeded();`,
64
+ ` uint32_t x = __tc_rand_state;`,
65
+ ` x ^= x << 13;`,
66
+ ` x ^= x >> 17;`,
67
+ ` x ^= x << 5;`,
68
+ ` __tc_rand_state = x;`,
69
+ ` return x;`,
70
+ `}`,
71
+ ``,
72
+ `// Re-seed the PRNG. After this call the sequence is deterministic from`,
73
+ `// \`seed\`, matching Arduino randomSeed. A literal zero seed is accepted and`,
74
+ `// remapped to the same odd constant (xorshift32 cannot start from 0).`,
75
+ `static inline void __tc_rand_seed(uint32_t seed) {`,
76
+ ` __tc_rand_state = (seed == 0U) ? 0x9E3779B9U : seed;`,
77
+ ` __tc_rand_seeded = true;`,
78
+ `}`,
79
+ ``,
80
+ `// Non-negative 31-bit integer [0, 2^31 - 1]. Mask the top bit so the value`,
81
+ `// is always non-negative even though int32_t is the HAL return type.`,
82
+ `static inline int32_t __tc_rand_int(void) {`,
83
+ ` return static_cast<int32_t>(__tc_rand_next() & 0x7FFFFFFFU);`,
84
+ `}`,
85
+ ``,
86
+ `// Half-open range [min, max-1]. Implements the HAL contract directly rather`,
87
+ `// than calling Arduino random(min, max): the modulus rejection-free form`,
88
+ `// avoids the modulo bias a naive (rand % (max-min)) introduces. max > min`,
89
+ `// is assumed (the HAL validates this at the source level).`,
90
+ `static inline int32_t __tc_rand_range(int32_t min, int32_t max) {`,
91
+ ` if (max <= min) { return min; }`,
92
+ ` uint32_t span = static_cast<uint32_t>(max) - static_cast<uint32_t>(min);`,
93
+ ` uint32_t r = __tc_rand_next();`,
94
+ ` return static_cast<int32_t>(min + static_cast<int32_t>(r % (span + 1U)));`,
95
+ `}`,
96
+ `// CUTTLEFISH_RANDOM_END`,
97
+ ``,
98
+ ];
99
+ }
100
+ /**
101
+ * Resolve a HAL random.* op to Zephyr C++ via the __tc_rand_* shim.
102
+ * Returns `{ code }` for the statement op (seed), `{ expression }` for the
103
+ * value-returning ops (int / range). The `default` arm throws the standard
104
+ * unsupported-op error so the manifest validator's per-op probe stays honest.
105
+ */
106
+ export function lowerRandom(op) {
107
+ const o = op;
108
+ switch (op.operation) {
109
+ case 'random.int':
110
+ return { expression: '__tc_rand_int()' };
111
+ case 'random.range': {
112
+ // When min is a literal 0 (the Random.upTo(max) collapse), emit the
113
+ // single-arg __tc_rand_range(0, max) form for readability — it is the
114
+ // exact equivalent range and matches what the HAL upTo path expects.
115
+ if (String(o.min) === '0')
116
+ return { expression: `__tc_rand_range(0, ${String(o.max)})` };
117
+ return { expression: `__tc_rand_range(${String(o.min)}, ${String(o.max)})` };
118
+ }
119
+ case 'random.seed': {
120
+ // The manifest validator's probe sends no seed field; tolerate it with a
121
+ // literal 0 (mirrors how wdt.lowering tolerates an absent timeout).
122
+ const seed = o.seed === undefined ? 0 : o.seed;
123
+ return { code: `__tc_rand_seed(static_cast<uint32_t>(${String(seed)}));` };
124
+ }
125
+ default:
126
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
127
+ `Open an issue or use rawCpp() to emit it manually.`);
128
+ }
129
+ }
@@ -0,0 +1,17 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the per-controller SPI state. The bus device resolves at compile time;
5
+ * a static spi_config holds the base operation flags, and mutable runtime fields
6
+ * hold the mode (CPOL/CPHA bits) + bit order (lsb) so set_mode/set_bit_order can
7
+ * rebuild operation at init time (Zephyr's spi_config.operation is the only knob).
8
+ */
9
+ export declare function spiInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[];
10
+ /**
11
+ * Resolve a HAL spi.* op to Zephyr C++.
12
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
13
+ */
14
+ export declare function lowerSpi(op: HALOpIR, chip: ZephyrChipDescriptor): {
15
+ code?: string;
16
+ expression?: string;
17
+ };
@@ -0,0 +1,113 @@
1
+ // ---------------------------------------------------------------------------
2
+ // SPI lowering — spi_transceive_dt against a spi_dt_spec; CS as GPIO
3
+ //
4
+ // Zephyr's `spi_transceive_dt(&spec, tx, rx)` is stateless per-call, so there
5
+ // is no pending-tx-buffer (unlike I2C). The CS pin is driven as a plain GPIO
6
+ // (like ESP32), since the DT spec's CS handling is configured at compile time
7
+ // but the HAL's explicit cs_low/cs_high ops want manual control.
8
+ //
9
+ // The board's SPI node (spi2 on the XIAO) has no pre-declared client device
10
+ // node, so the lowering cannot use SPI_DT_SPEC_GET (which needs a client node).
11
+ // Instead we build a `struct spi_config` at runtime against
12
+ // DEVICE_DT_GET(DT_NODELABEL(spi2)) and call spi_transceive directly.
13
+ // ---------------------------------------------------------------------------
14
+ import { parseControllerIndex } from './util.js';
15
+ import { controllerNodelabelForPin } from '../chips/controllers.js';
16
+ /** The C variable prefix for a controller's state. */
17
+ function prefix(idx) {
18
+ return `__tc_spi${idx}`;
19
+ }
20
+ /**
21
+ * Emit the per-controller SPI state. The bus device resolves at compile time;
22
+ * a static spi_config holds the base operation flags, and mutable runtime fields
23
+ * hold the mode (CPOL/CPHA bits) + bit order (lsb) so set_mode/set_bit_order can
24
+ * rebuild operation at init time (Zephyr's spi_config.operation is the only knob).
25
+ */
26
+ export function spiInitLines(chip, controllerIndex) {
27
+ const ctrl = chip.spi?.controllers[controllerIndex];
28
+ if (!ctrl)
29
+ return [];
30
+ const p = prefix(controllerIndex);
31
+ return [
32
+ '// CUTTLEFISH_SPI_BEGIN',
33
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
34
+ `static bool ${p}_ready = false;`,
35
+ `static uint8_t ${p}_mode = 0; // bit0=CPOL, bit1=CPHA`,
36
+ `static bool ${p}_lsb = false; // false=MSB (default), true=LSB`,
37
+ `static struct spi_config ${p}_cfg = {`,
38
+ ` .frequency = 1000000,`,
39
+ `};`,
40
+ `static void ${p}_init(void) {`,
41
+ ` if (!${p}_ready) {`,
42
+ ` ${p}_cfg.bus = ${p}_dev;`,
43
+ ` ${p}_cfg.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)`,
44
+ ` | (${p}_lsb ? SPI_TRANSFER_LSB : SPI_TRANSFER_MSB)`,
45
+ ` | ((${p}_mode & 0x1) ? SPI_MODE_CPOL : 0)`,
46
+ ` | ((${p}_mode & 0x2) ? SPI_MODE_CPHA : 0);`,
47
+ ` ${p}_ready = true;`,
48
+ ` }`,
49
+ `}`,
50
+ '// CUTTLEFISH_SPI_END',
51
+ ];
52
+ }
53
+ /**
54
+ * Resolve a HAL spi.* op to Zephyr C++.
55
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
56
+ */
57
+ export function lowerSpi(op, chip) {
58
+ const o = op;
59
+ const idx = parseControllerIndex(o.bus);
60
+ const p = prefix(idx);
61
+ switch (op.operation) {
62
+ case 'spi.begin':
63
+ return { code: `${p}_init();` };
64
+ case 'spi.end':
65
+ return { code: `spi_release(${p}_dev, &${p}_cfg);` };
66
+ case 'spi.begin_transaction':
67
+ // Config is static; transaction begin is a no-op (frequency/mode baked in).
68
+ return { code: `${p}_init();` };
69
+ case 'spi.end_transaction':
70
+ return { code: `(void)0;` };
71
+ case 'spi.set_mode':
72
+ // Apply CPOL/CPHA: store the mode byte then re-init so the next transfer
73
+ // picks up the rebuilt operation flags. The ready flag is cleared so
74
+ // _init() rebuilds rather than early-returning.
75
+ return { code: `{ ${p}_mode = static_cast<uint8_t>(${o.mode}); ${p}_ready = false; ${p}_init(); }` };
76
+ case 'spi.set_bit_order': {
77
+ // The HAL payload `order` is a string ("lsb" | "msb"), per SpiSetBitOrderOp.
78
+ // Normalize here in TS so we emit a boolean literal, not the raw string
79
+ // (which would be an undeclared C++ identifier).
80
+ const key = String(o.order).replace(/^["']|["']$/g, '').toLowerCase();
81
+ const lsb = (key === 'lsb' || key === 'lsbfirst') ? 'true' : 'false';
82
+ return { code: `{ ${p}_lsb = ${lsb}; ${p}_ready = false; ${p}_init(); }` };
83
+ }
84
+ case 'spi.transfer': {
85
+ // Single-byte full-duplex, returns the received byte (GCC stmt-expr).
86
+ return {
87
+ expression: `({ uint8_t __tx = static_cast<uint8_t>(${o.data}); uint8_t __rx = 0; struct spi_buf __tb = { .buf = &__tx, .len = 1 }; struct spi_buf_set __tbs = { .buffers = &__tb, .count = 1 }; struct spi_buf __rb = { .buf = &__rx, .len = 1 }; struct spi_buf_set __rbs = { .buffers = &__rb, .count = 1 }; ${p}_init(); spi_transceive(${p}_dev, &${p}_cfg, &__tbs, &__rbs); __rx; })`,
88
+ };
89
+ }
90
+ case 'spi.read_buffer': {
91
+ // Read count bytes by sending 0xFF dummy bytes (full-duplex read).
92
+ const count = o.count;
93
+ return {
94
+ code: `{ uint8_t __dummy[${count}] = {0}; for (int __i = 0; __i < (int)(${count}); __i++) __dummy[__i] = 0xFF; struct spi_buf __tb = { .buf = __dummy, .len = ${count} }; struct spi_buf_set __tbs = { .buffers = &__tb, .count = 1 }; struct spi_buf __rb = { .buf = reinterpret_cast<void*>(${o.buffer}), .len = ${count} }; struct spi_buf_set __rbs = { .buffers = &__rb, .count = 1 }; ${p}_init(); spi_transceive(${p}_dev, &${p}_cfg, &__tbs, &__rbs); }`,
95
+ };
96
+ }
97
+ case 'spi.cs_low':
98
+ case 'spi.cs_high': {
99
+ // CS driven as a plain GPIO via the owning controller (the CS pin comes
100
+ // from the op's `pin` field; Zephyr uses gpio_pin_set_raw). Resolve the
101
+ // controller by pin so a CS on a high-numbered pin (ESP32-S3 gpio1) lands
102
+ // on the right node.
103
+ const val = op.operation === 'spi.cs_low' ? 0 : 1;
104
+ const gpioController = controllerNodelabelForPin(chip, o.pin);
105
+ return {
106
+ code: `gpio_pin_set_raw(DEVICE_DT_GET(DT_NODELABEL(${gpioController})), ${o.pin}, ${val});`,
107
+ };
108
+ }
109
+ default:
110
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
111
+ `Open an issue or use rawCpp() to emit it manually.`);
112
+ }
113
+ }
@@ -0,0 +1,9 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Resolve a HAL timing.* op to Zephyr C++.
4
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
5
+ */
6
+ export declare function lowerTiming(op: HALOpIR): {
7
+ code?: string;
8
+ expression?: string;
9
+ };
@@ -0,0 +1,63 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Timing lowering — Zephyr kernel timing
3
+ //
4
+ // delay/millis/delay_microseconds/micros/free_heap lower directly. The four
5
+ // timer ops (set_interval/set_timeout/clear_interval/clear_timeout) call the
6
+ // timer_methods polyfill helpers (k_timer + k_work pool), declared as 'polyfill'
7
+ // status in the manifest — the validator skips the resolver probe for these
8
+ // (they legitimately return polyfill-helper calls, not direct lowering).
9
+ // free_heap has no portable Zephyr query without CONFIG_SYS_HEAP_RUNTIME_STATS;
10
+ // it returns 0 with a comment (honest limitation).
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Resolve a HAL timing.* op to Zephyr C++.
14
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
15
+ */
16
+ export function lowerTiming(op) {
17
+ const o = op;
18
+ switch (op.operation) {
19
+ case 'timing.delay': {
20
+ // Milliseconds — k_msleep is the blocking Zephyr call. Because
21
+ // isRtosTarget() returns true, the transpiler does not warn about
22
+ // blocking delays inside loop().
23
+ const ms = o.ms;
24
+ return { code: `k_msleep(${ms});` };
25
+ }
26
+ case 'timing.delay_microseconds': {
27
+ // k_busy_wait spins (does not yield); for cooperative μs delays.
28
+ return { code: `k_busy_wait(${o.us});` };
29
+ }
30
+ case 'timing.millis':
31
+ // k_uptime_get_32() returns int64_t milliseconds since boot. Cast to the
32
+ // Arduino-millis() return type (uint32_t) so overflow wraps identically.
33
+ return { expression: 'static_cast<uint32_t>(k_uptime_get_32())' };
34
+ case 'timing.micros': {
35
+ // Convert hardware cycles to microseconds. k_cycle_get_32 + the cycles/sec
36
+ // macro (note: sys_clock_hw_cycles_per_sec is a MACRO — needs parens).
37
+ return {
38
+ expression: '(uint32_t)(((uint64_t)k_cycle_get_32() * 1000000ULL) / sys_clock_hw_cycles_per_sec())',
39
+ };
40
+ }
41
+ case 'timing.free_heap':
42
+ // No portable free-heap query without CONFIG_SYS_HEAP_RUNTIME_STATS.
43
+ // Return 0 with a comment so callers don't get a link error.
44
+ return { expression: '(0 /* free_heap: enable CONFIG_SYS_HEAP_RUNTIME_STATS for real value */)' };
45
+ case 'timing.set_interval':
46
+ // Backed by the timer_methods polyfill (k_timer + k_work). The handler is
47
+ // the resolved C++ callback name; timeout is the repeat period (ms).
48
+ // NOTE: these ops are declared 'polyfill' in the manifest, so setInterval
49
+ // is rewritten to __tc_setInterval before reaching the lowering — these
50
+ // cases are a fallback/defense and use the real op fields (handler/timeout).
51
+ return { expression: `__tc_setInterval(${o.handler}, ${o.timeout})` };
52
+ case 'timing.set_timeout':
53
+ // One-shot: k_timer with K_FOREVER period.
54
+ return { expression: `__tc_setTimeout(${o.handler}, ${o.timeout})` };
55
+ case 'timing.clear_interval':
56
+ return { code: `__tc_clearInterval(${o.id});` };
57
+ case 'timing.clear_timeout':
58
+ return { code: `__tc_clearTimeout(${o.id});` };
59
+ default:
60
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
61
+ `Open an issue or use rawCpp() to emit it manually.`);
62
+ }
63
+ }
@@ -0,0 +1,10 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Resolve a HAL tone.* op to Zephyr C++ via PWM.
5
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
6
+ */
7
+ export declare function lowerTone(op: HALOpIR, chip: ZephyrChipDescriptor): {
8
+ code?: string;
9
+ expression?: string;
10
+ };
@@ -0,0 +1,50 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Tone lowering — PWM-based square-wave generation
3
+ //
4
+ // tone is a convenience wrapper around PWM: a square wave at `frequency` Hz with
5
+ // a 50% duty cycle. The lowering maps frequency → period (ns) and sets the PWM
6
+ // channel to a 50% pulse. stop turns the output off (0% duty). The PWM spec is
7
+ // resolved from the chip descriptor (pwm-led0 on the XIAO).
8
+ //
9
+ // An optional duration is honored by scheduling a stop via k_sleep in a detached
10
+ // fashion — but the simple lowering is blocking (k_msleep(duration) then stop),
11
+ // matching the synchronous Arduino tone() semantics. For non-blocking tone,
12
+ // a workqueue would be needed (deferred).
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Resolve a HAL tone.* op to Zephyr C++ via PWM.
16
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
17
+ */
18
+ export function lowerTone(op, chip) {
19
+ const o = op;
20
+ switch (op.operation) {
21
+ case 'tone.play': {
22
+ // period_ns = 1e9 / freq; pulse_ns = period / 2 (50% duty).
23
+ // Build a fake pwm.write would need the spec var directly; emit the
24
+ // pwm_set_dt call against the same spec var the PWM lowering uses.
25
+ const spec = chip.pwm?.specs[0];
26
+ if (!spec) {
27
+ return { code: `/* tone.play(${o.frequency}): no PWM spec in chip descriptor */` };
28
+ }
29
+ const v = `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
30
+ const freq = o.frequency;
31
+ const duration = o.duration;
32
+ const setTone = `uint32_t __period = (${freq} > 0) ? (1000000000ULL / static_cast<uint64_t>(${freq})) : 0; pwm_set_dt(&${v}, __period, __period / 2);`;
33
+ if (duration !== undefined) {
34
+ // Blocking tone for the requested duration, then stop.
35
+ return { code: `${setTone} k_msleep(${duration}); pwm_set_pulse_dt(&${v}, 0);` };
36
+ }
37
+ return { code: setTone };
38
+ }
39
+ case 'tone.stop': {
40
+ const spec = chip.pwm?.specs[0];
41
+ if (!spec)
42
+ return { code: `/* tone.stop: no PWM spec */` };
43
+ const v = `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
44
+ return { code: `pwm_set_pulse_dt(&${v}, 0);` };
45
+ }
46
+ default:
47
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
48
+ `Open an issue or use rawCpp() to emit it manually.`);
49
+ }
50
+ }
@@ -0,0 +1,15 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the per-controller UART device + an init helper. Called from shimLines
5
+ * when the program uses UART.
6
+ */
7
+ export declare function uartInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[];
8
+ /**
9
+ * Resolve a HAL uart.* op to Zephyr C++.
10
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
11
+ */
12
+ export declare function lowerUart(op: HALOpIR): {
13
+ code?: string;
14
+ expression?: string;
15
+ };
@@ -0,0 +1,102 @@
1
+ // ---------------------------------------------------------------------------
2
+ // UART lowering — uart_poll_out / uart_poll_in per-byte
3
+ //
4
+ // Zephyr's UART API is byte-oriented (uart_poll_out / uart_poll_in). The HAL's
5
+ // uart.print/println/write lower to per-byte poll_out loops. For console output
6
+ // the strategy's transformConsoleCall routes to printk; these uart.* ops are
7
+ // for a specific UART port (the XIAO exposes uart0 on D6/D7).
8
+ //
9
+ // The device resolves at compile time via DEVICE_DT_GET(DT_NODELABEL(uart0)).
10
+ // uart.begin configures the baud via uart_configure.
11
+ // ---------------------------------------------------------------------------
12
+ import { parseControllerIndex } from './util.js';
13
+ /** The C variable prefix for a controller's state. */
14
+ function prefix(idx) {
15
+ return `__tc_uart${idx}`;
16
+ }
17
+ /**
18
+ * Emit the per-controller UART device + an init helper. Called from shimLines
19
+ * when the program uses UART.
20
+ */
21
+ export function uartInitLines(chip, controllerIndex) {
22
+ const ctrl = chip.uart?.controllers[controllerIndex];
23
+ if (!ctrl)
24
+ return [];
25
+ const p = prefix(controllerIndex);
26
+ return [
27
+ '// CUTTLEFISH_UART_BEGIN',
28
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
29
+ `static void ${p}_init(uint32_t baud) {`,
30
+ ` const struct uart_config cfg = { .baudrate = (baud ? baud : 115200), .parity = UART_CFG_PARITY_NONE, .stop_bits = UART_CFG_STOP_BITS_1, .data_bits = UART_CFG_DATA_BITS_8, .flow_ctrl = UART_CFG_FLOW_CTRL_NONE };`,
31
+ ` uart_configure(${p}_dev, &cfg);`,
32
+ `}`,
33
+ '// CUTTLEFISH_UART_END',
34
+ ];
35
+ }
36
+ /** Render a string-literal or expression to a per-byte poll_out loop. */
37
+ function renderWrite(dev, value, newline) {
38
+ // String literal → emit a char-array loop (known length). Otherwise fall back
39
+ // to a const char* cast with a strlen-guarded loop.
40
+ if (/^".*"$/.test(value)) {
41
+ const body = `for (size_t __i = 0; __i < sizeof(${value}) - 1; __i++) { uart_poll_out(${dev}, (${value})[__i]); }`;
42
+ return newline ? `${body} uart_poll_out(${dev}, '\\n');` : body;
43
+ }
44
+ const body = `for (const char* __s = (const char*)(${value}); *__s; __s++) { uart_poll_out(${dev}, *__s); }`;
45
+ return newline ? `${body} uart_poll_out(${dev}, '\\n');` : body;
46
+ }
47
+ /**
48
+ * Resolve a HAL uart.* op to Zephyr C++.
49
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
50
+ */
51
+ export function lowerUart(op) {
52
+ const o = op;
53
+ const idx = parseControllerIndex(o.port);
54
+ const p = prefix(idx);
55
+ const dev = `${p}_dev`;
56
+ switch (op.operation) {
57
+ case 'uart.begin':
58
+ return { code: `${p}_init(static_cast<uint32_t>(${o.baud}));` };
59
+ case 'uart.end':
60
+ return { code: `(void)${dev};` };
61
+ case 'uart.print':
62
+ return { code: renderWrite(dev, o.value, false) };
63
+ case 'uart.println':
64
+ return { code: renderWrite(dev, o.value, true) };
65
+ case 'uart.write':
66
+ return { code: renderWrite(dev, o.data, false) };
67
+ case 'uart.printf': {
68
+ // snprintf into a buffer, then poll_out each byte.
69
+ const fmt = o.format;
70
+ const args = (o.args ?? []).join(', ');
71
+ const argList = args ? `, ${args}` : '';
72
+ return {
73
+ code: `char __buf[128]; int __n = snprintk(__buf, sizeof(__buf), ${fmt}${argList}); for (int __i = 0; __i < __n; __i++) { uart_poll_out(${dev}, __buf[__i]); }`,
74
+ };
75
+ }
76
+ case 'uart.read':
77
+ // Non-blocking poll; returns the byte or -1 if none available.
78
+ return { expression: `({ unsigned char __b = 0; (uart_poll_in(${dev}, &__b) == 0) ? (int)__b : -1; })` };
79
+ case 'uart.peek':
80
+ // The poll API has no buffered-byte store, so there is no true peek.
81
+ // Return -1 (the Arduino "no data" sentinel) rather than blocking. This
82
+ // is an honest limitation of the byte-level poll driver; an interrupt- or
83
+ // DMA-backed UART driver would be needed for real peek semantics.
84
+ return { expression: '(-1)' };
85
+ case 'uart.available':
86
+ // The poll API reports only "at least one byte ready" via uart_poll_in's
87
+ // return code — it has no buffered-byte count, and probing with poll_in
88
+ // would DRAIN the very byte the caller next wants to read. So we cannot
89
+ // honestly report availability. Return 0 (Arduino's "no data" value)
90
+ // rather than the old truthy -1, so `if (uart.available())` loops don't
91
+ // spin forever on a false-positive. Callers should instead just call
92
+ // uart.read() directly (it returns -1 when no byte is ready). For true
93
+ // buffered availability, use an interrupt/DMA-backed UART driver.
94
+ return { expression: '(0)' };
95
+ case 'uart.flush':
96
+ // poll_out is synchronous (blocking until sent); flush is a no-op.
97
+ return { code: `(void)${dev};` };
98
+ default:
99
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
100
+ `Open an issue or use rawCpp() to emit it manually.`);
101
+ }
102
+ }
@@ -0,0 +1,4 @@
1
+ /** Parse a peripheral bus/port string like "UART0" / "I2C1" / "SPI2" → numeric
2
+ * index. The HAL op stream carries the bus as a TS-side name (I2C0, SPI0);
3
+ * the index selects which controller in the chip descriptor is used. */
4
+ export declare function parseControllerIndex(busOrPort: string | undefined): number;
@@ -0,0 +1,12 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared lowering utilities
3
+ // ---------------------------------------------------------------------------
4
+ /** Parse a peripheral bus/port string like "UART0" / "I2C1" / "SPI2" → numeric
5
+ * index. The HAL op stream carries the bus as a TS-side name (I2C0, SPI0);
6
+ * the index selects which controller in the chip descriptor is used. */
7
+ export function parseControllerIndex(busOrPort) {
8
+ if (!busOrPort)
9
+ return 0;
10
+ const m = busOrPort.match(/(\d+)$/);
11
+ return m ? parseInt(m[1], 10) : 0;
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the WDT device + channel state. Called from shimLines when the program
5
+ * uses the watchdog.
6
+ */
7
+ export declare function wdtInitLines(chip: ZephyrChipDescriptor): string[];
8
+ /**
9
+ * Resolve a HAL wdt.* op to Zephyr C++.
10
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
11
+ */
12
+ export declare function lowerWdt(op: HALOpIR): {
13
+ code?: string;
14
+ expression?: string;
15
+ };
@@ -0,0 +1,80 @@
1
+ // ---------------------------------------------------------------------------
2
+ // WDT lowering — nRF watchdog via wdt0
3
+ //
4
+ // Zephyr's WDT API: install a timeout (before setup), then setup, then feed
5
+ // periodically. The lowering caches the channel id returned by
6
+ // wdt_install_timeout in a static var so wdt.reset can feed it.
7
+ //
8
+ // enable(timeout): wdt_install_timeout + wdt_setup. Zephyr expects the timeout
9
+ // in milliseconds (wdt_window.max). The HAL passes a "250ms" string or WDTO_*
10
+ // constant or a number; we parse to ms in the lowering.
11
+ // ---------------------------------------------------------------------------
12
+ /** Parse a HAL wdt timeout ("250ms", WDTO_2S, or a bare number) to ms.
13
+ * Tolerates undefined (the manifest validator's probe sends a minimal op). */
14
+ function timeoutToMs(timeout) {
15
+ if (typeof timeout === 'number')
16
+ return timeout;
17
+ if (!timeout)
18
+ return 1000; // default 1s when absent (e.g. the validator probe)
19
+ // Arduino WDTO_* constants.
20
+ const wdto = {
21
+ WDTO_15MS: 15, WDTO_30MS: 30, WDTO_60MS: 60, WDTO_120MS: 120,
22
+ WDTO_250MS: 250, WDTO_500MS: 500, WDTO_1S: 1000, WDTO_2S: 2000,
23
+ WDTO_4S: 4000, WDTO_8S: 8000,
24
+ };
25
+ if (wdto[timeout])
26
+ return wdto[timeout];
27
+ const m = timeout.match(/^(\d+)\s*ms$/i);
28
+ if (m)
29
+ return parseInt(m[1], 10);
30
+ const s = timeout.match(/^(\d+)\s*s$/i);
31
+ if (s)
32
+ return parseInt(s[1], 10) * 1000;
33
+ const n = parseInt(timeout, 10);
34
+ return isNaN(n) ? 1000 : n;
35
+ }
36
+ /**
37
+ * Emit the WDT device + channel state. Called from shimLines when the program
38
+ * uses the watchdog.
39
+ */
40
+ export function wdtInitLines(chip) {
41
+ const nodeLabel = chip.wdt?.nodeLabel ?? 'wdt0';
42
+ return [
43
+ '// CUTTLEFISH_WDT_BEGIN',
44
+ `static const struct device* __tc_wdt_dev = DEVICE_DT_GET(DT_NODELABEL(${nodeLabel}));`,
45
+ 'static int __tc_wdt_channel = -1;',
46
+ 'static bool __tc_wdt_setup_done = false;',
47
+ '// CUTTLEFISH_WDT_END',
48
+ ];
49
+ }
50
+ /**
51
+ * Resolve a HAL wdt.* op to Zephyr C++.
52
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
53
+ */
54
+ export function lowerWdt(op) {
55
+ const o = op;
56
+ switch (op.operation) {
57
+ case 'wdt.enable': {
58
+ const ms = timeoutToMs(o.timeout);
59
+ // Install a timeout then set up the watchdog. Both are idempotent via
60
+ // the static flags. WDT_FLAG_RESET_CPU_CORE = full reset on timeout.
61
+ return {
62
+ code: [
63
+ `if (!__tc_wdt_setup_done) {`,
64
+ ` const struct wdt_timeout_cfg __cfg = { .window = { .min = 0, .max = ${ms} }, .callback = NULL, .flags = WDT_FLAG_RESET_CPU_CORE };`,
65
+ ` __tc_wdt_channel = wdt_install_timeout(__tc_wdt_dev, &__cfg);`,
66
+ ` wdt_setup(__tc_wdt_dev, WDT_OPT_PAUSE_HALTED_BY_DBG);`,
67
+ ` __tc_wdt_setup_done = true;`,
68
+ `}`,
69
+ ].join(' '),
70
+ };
71
+ }
72
+ case 'wdt.reset':
73
+ return { code: `if (__tc_wdt_channel >= 0) { wdt_feed(__tc_wdt_dev, __tc_wdt_channel); }` };
74
+ case 'wdt.disable':
75
+ return { code: `wdt_disable(__tc_wdt_dev); __tc_wdt_setup_done = false;` };
76
+ default:
77
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
78
+ `Open an issue or use rawCpp() to emit it manually.`);
79
+ }
80
+ }
@@ -0,0 +1,21 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * The WiFi runtime shim. All helpers are `static` so unused ones don't trip
4
+ * -Wunused-function in the single generated TU (mirrors ble.ts).
5
+ *
6
+ * State is driven by two net_mgmt event handlers:
7
+ * - L4 connectivity (NET_EVENT_L4_CONNECTED/DISCONNECTED) → connected flag.
8
+ * This reflects IP connectivity (post-DHCP), not just the WiFi link — the
9
+ * events are raised by conn_mgr's monitoring layer (conn_mgr_monitor.c).
10
+ * - WiFi scan (NET_EVENT_WIFI_SCAN_RESULT/SCAN_DONE) → scan_results pool.
11
+ */
12
+ export declare function wifiInitLines(): string[];
13
+ /**
14
+ * Resolve a HAL wifi.* op to Zephyr C++ via the __tc_wifi_* shim. Returns
15
+ * `{ code }` for statement ops, `{ expression }` for value-returning ops,
16
+ * `undefined` for out-of-scope ops (the manifest declares those unsupported).
17
+ */
18
+ export declare function lowerWifi(op: HALOpIR): {
19
+ code?: string;
20
+ expression?: string;
21
+ } | undefined;