@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -0,0 +1,27 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /** Which i2s.* verbs the program actually uses on a controller — the init
4
+ * block emits only the state those verbs reference (a write-only program
5
+ * must not carry an unused rx buffer: -Werror fails the build). */
6
+ export interface I2sUsage {
7
+ write: boolean;
8
+ read: boolean;
9
+ readAt: boolean;
10
+ }
11
+ /**
12
+ * Emit the per-controller I2S state: device handle, mem slab, TX/RX block
13
+ * buffers, and the lazy per-direction init helpers. Called from shimLines
14
+ * when the program uses i2s.* on this controller and the chip declares it.
15
+ * The once-flags live HERE (shim scope), not in the lowered call sites — a
16
+ * second write() statement must not reconfigure/re-start a running stream
17
+ * (i2s_configure is legal only before the first trigger).
18
+ */
19
+ export declare function i2sInitLines(chip: ZephyrChipDescriptor, controllerIndex: number, blockBytes: number, usage: I2sUsage): string[];
20
+ /**
21
+ * Resolve a HAL i2s.* op to Zephyr C++.
22
+ * Returns `{ code }` for write, `{ expression }` for read/readAt.
23
+ */
24
+ export declare function lowerI2s(op: HALOpIR, chip: ZephyrChipDescriptor): {
25
+ code?: string;
26
+ expression?: string;
27
+ };
@@ -0,0 +1,98 @@
1
+ // ---------------------------------------------------------------------------
2
+ // I2S lowering — Zephyr's i2s API over the harvested controller node
3
+ //
4
+ // The controller is addressed by its DT nodelabel (chip.i2s, the harvested
5
+ // i2s@ node — ESP32 I2S0/1). Each direction lazy-inits on first use (the
6
+ // PWM discipline): configure + trigger START once, then every write/read is
7
+ // one block. The shim owns the mem slab (2 blocks), the TX packing buffer,
8
+ // and the RX receive buffer; blockAt/readAt index the last received block.
9
+ // ---------------------------------------------------------------------------
10
+ /** The C variable names for an I2S controller's state. */
11
+ function prefix(idx) {
12
+ return `__tc_i2s${idx}`;
13
+ }
14
+ /**
15
+ * Emit the per-controller I2S state: device handle, mem slab, TX/RX block
16
+ * buffers, and the lazy per-direction init helpers. Called from shimLines
17
+ * when the program uses i2s.* on this controller and the chip declares it.
18
+ * The once-flags live HERE (shim scope), not in the lowered call sites — a
19
+ * second write() statement must not reconfigure/re-start a running stream
20
+ * (i2s_configure is legal only before the first trigger).
21
+ */
22
+ export function i2sInitLines(chip, controllerIndex, blockBytes, usage) {
23
+ const ctrl = chip.i2s?.controllers[controllerIndex];
24
+ if (!ctrl)
25
+ return [];
26
+ const p = prefix(controllerIndex);
27
+ // readAt rides the RX engine too (its lowering lazily starts RX like read
28
+ // does) — a readAt-only program still references the whole engine state.
29
+ const needsEngine = usage.write || usage.read || usage.readAt;
30
+ const lines = [
31
+ '// CUTTLEFISH_I2S_BEGIN',
32
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
33
+ ];
34
+ if (needsEngine) {
35
+ lines.push(`K_MEM_SLAB_DEFINE_STATIC(${p}_slab, ${blockBytes}, 2, 4);`, `K_MEM_SLAB_DEFINE_STATIC(${p}_rxslab, ${blockBytes}, 2, 4);`, `static void ${p}_init(uint32_t hz, uint8_t channels, uint8_t bits, size_t block_bytes) {`, ' struct i2s_config cfg = { 0 };', ' cfg.word_size = bits;', ' cfg.channels = channels;', ' cfg.format = I2S_FMT_DATA_FORMAT_I2S;', ' cfg.options = 0;', ' cfg.frame_clk_freq = hz;', ` cfg.mem_slab = &${p}_slab;`, ' cfg.block_size = block_bytes;', ' cfg.timeout = 200;', ` int err = i2s_configure(${p}_dev, I2S_DIR_TX, &cfg);`, ' if (err != 0) { printk("typecad-hal i2s: tx configure failed: %d\\n", err); }', ` cfg.mem_slab = &${p}_rxslab;`, ` err = i2s_configure(${p}_dev, I2S_DIR_RX, &cfg);`, ' if (err != 0) { printk("typecad-hal i2s: rx configure failed: %d\\n", err); }', '}', `static bool ${p}_init_done = false;`);
36
+ }
37
+ if (usage.write) {
38
+ lines.push(`static int16_t ${p}_txbuf[${blockBytes / 2}];`, `static bool ${p}_tx_started = false;`);
39
+ }
40
+ if (usage.read || usage.readAt) {
41
+ lines.push(`static int16_t ${p}_rxbuf[${blockBytes / 2}];`);
42
+ lines.push(`static bool ${p}_rx_started = false;`);
43
+ }
44
+ lines.push('// CUTTLEFISH_I2S_END');
45
+ return lines;
46
+ }
47
+ /**
48
+ * Resolve a HAL i2s.* op to Zephyr C++.
49
+ * Returns `{ code }` for write, `{ expression }` for read/readAt.
50
+ */
51
+ export function lowerI2s(op, chip) {
52
+ const o = op;
53
+ // DAC discipline: no harvested i2s@ node lowers to the honest comment.
54
+ if (!chip.i2s || chip.i2s.controllers.length === 0) {
55
+ return { code: `/* ${op.operation}: this board's chip data declares no I2S controller (no harvested i2s@ node) */` };
56
+ }
57
+ const idx = Math.min(Number(o.instance ?? 0), chip.i2s.controllers.length - 1);
58
+ const p = prefix(idx);
59
+ const hz = Number(o.hz ?? 16000);
60
+ const channels = Number(o.channels ?? 2);
61
+ const bits = Number(o.bits ?? 16);
62
+ const blockFrames = Number(o.blockFrames ?? 64);
63
+ const blockBytes = blockFrames * channels * (bits / 8);
64
+ switch (op.operation) {
65
+ case 'i2s.write': {
66
+ const samples = (o.samples ?? []);
67
+ const cap = blockFrames * channels;
68
+ if (samples.length > cap) {
69
+ throw new Error(`I2S write: one block carries at most ${cap} samples (${blockFrames} frames × ${channels} channels) — got ${samples.length}.`);
70
+ }
71
+ // Zero-pad to the full block; samples are 16-bit.
72
+ const writes = [];
73
+ for (let i = 0; i < cap; i++) {
74
+ const v = i < samples.length ? samples[i] : 0;
75
+ writes.push(`${p}_txbuf[${i}] = static_cast<int16_t>(${v});`);
76
+ }
77
+ return {
78
+ code: `{ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } ${writes.join(' ')} int err = i2s_buf_write(${p}_dev, ${p}_txbuf, ${blockBytes}U); if (err != 0) { printk("typecad-hal i2s: write failed: %d\\n", err); } else { if (!${p}_tx_started) { ${p}_tx_started = true; err = i2s_trigger(${p}_dev, I2S_DIR_TX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: tx start failed: %d\\n", err); } } } }`,
79
+ };
80
+ }
81
+ case 'i2s.read': {
82
+ return {
83
+ expression: `({ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } if (!${p}_rx_started) { ${p}_rx_started = true; int err = i2s_trigger(${p}_dev, I2S_DIR_RX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: rx start failed: %d\\n", err); } } size_t got = 0U; int err = i2s_buf_read(${p}_dev, ${p}_rxbuf, &got); if (err != 0) { printk("typecad-hal i2s: read failed: %d\\n", err); } static_cast<double>(${p}_rxbuf[0]); })`,
84
+ };
85
+ }
86
+ case 'i2s.read_at': {
87
+ // Same lazy RX start as read(): readAt indexes the last RECEIVED
88
+ // block, so the stream must be running — and a readAt-only program
89
+ // references (and therefore compiles in) the whole engine state.
90
+ return {
91
+ expression: `({ if (!${p}_init_done) { ${p}_init_done = true; ${p}_init(${hz}U, ${channels}U, ${bits}U, ${blockBytes}U); } if (!${p}_rx_started) { ${p}_rx_started = true; int err = i2s_trigger(${p}_dev, I2S_DIR_RX, I2S_TRIGGER_START); if (err != 0) { printk("typecad-hal i2s: rx start failed: %d\\n", err); } } int32_t at = static_cast<int32_t>(${o.index}); double v = 0.0; if (at >= 0 && static_cast<uint32_t>(at) < (sizeof(${p}_rxbuf) / sizeof(${p}_rxbuf[0]))) { v = static_cast<double>(${p}_rxbuf[at]); } v; })`,
92
+ };
93
+ }
94
+ default:
95
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
96
+ `Open an issue or use rawCpp() to emit it manually.`);
97
+ }
98
+ }
@@ -21,7 +21,15 @@ import { lowerFs } from './fs.js';
21
21
  import { lowerSensor } from './sensor.js';
22
22
  import { lowerHwtimer, lowerCounter } from './hwtimer.js';
23
23
  import { lowerThread } from './thread.js';
24
- export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, };
24
+ import { lowerTrace } from './trace.js';
25
+ import { lowerStrip } from './strip.js';
26
+ import { lowerHid } from './hid.js';
27
+ import { lowerMatrix } from './matrix.js';
28
+ import { lowerPower } from './power.js';
29
+ import { lowerClock } from './clock.js';
30
+ import { lowerCan } from './can.js';
31
+ import { lowerI2s } from './i2s.js';
32
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, lowerTrace, lowerStrip, lowerHid, lowerMatrix, lowerPower, lowerClock, lowerCan, lowerI2s, };
25
33
  /**
26
34
  * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
27
35
  * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
@@ -38,7 +38,15 @@ import { lowerFs } from './fs.js';
38
38
  import { lowerSensor } from './sensor.js';
39
39
  import { lowerHwtimer, lowerCounter } from './hwtimer.js';
40
40
  import { lowerThread } from './thread.js';
41
- export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, };
41
+ import { lowerTrace } from './trace.js';
42
+ import { lowerStrip } from './strip.js';
43
+ import { lowerHid } from './hid.js';
44
+ import { lowerMatrix } from './matrix.js';
45
+ import { lowerPower } from './power.js';
46
+ import { lowerClock } from './clock.js';
47
+ import { lowerCan } from './can.js';
48
+ import { lowerI2s } from './i2s.js';
49
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerUsb, lowerInterrupt, lowerWdt, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, lowerDac, lowerFs, lowerHwtimer, lowerCounter, lowerSensor, lowerThread, lowerTrace, lowerStrip, lowerHid, lowerMatrix, lowerPower, lowerClock, lowerCan, lowerI2s, };
42
50
  /**
43
51
  * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
44
52
  * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
@@ -94,6 +102,22 @@ export function lowerHalOp(op) {
94
102
  return lowerSensor(op);
95
103
  if (op.operation.startsWith('thread.'))
96
104
  return lowerThread(op);
105
+ if (op.operation.startsWith('trace.'))
106
+ return lowerTrace(op);
107
+ if (op.operation.startsWith('strip.'))
108
+ return lowerStrip(op, chip);
109
+ if (op.operation.startsWith('hid.'))
110
+ return lowerHid(op, chip);
111
+ if (op.operation.startsWith('matrix.'))
112
+ return lowerMatrix(op);
113
+ if (op.operation.startsWith('power.'))
114
+ return lowerPower(op, chip);
115
+ if (op.operation.startsWith('clock.'))
116
+ return lowerClock(op);
117
+ if (op.operation.startsWith('can.'))
118
+ return lowerCan(op, chip);
119
+ if (op.operation.startsWith('i2s.'))
120
+ return lowerI2s(op, chip);
97
121
  // raw / snprintf.emit / display.* / ... — not lowered by this
98
122
  // framework. Return undefined so the transpiler falls back and the manifest
99
123
  // validator confirms the unsupported declaration.
@@ -92,6 +92,12 @@ export function collectInterruptPins(program) {
92
92
  }
93
93
  };
94
94
  visit(program);
95
+ // Ops resolved to text while inlining one HAL call into another never become
96
+ // IR nodes — they ride on ProgramIR.resolvedHalOps (see strategy.ts's
97
+ // visitResolvedHalOps for the full story).
98
+ for (const op of program?.resolvedHalOps ?? []) {
99
+ visit({ operation: op });
100
+ }
95
101
  return pins;
96
102
  }
97
103
  /**
@@ -0,0 +1,15 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Emit the matrix trampoline state. Called from shimLines when the program
4
+ * uses matrix.* — the listener targets the synthesized tc_matrix node, the
5
+ * handler pointer is assigned by the on_key lowering.
6
+ */
7
+ export declare function matrixInitLines(): string[];
8
+ /**
9
+ * Resolve a HAL matrix.* op to Zephyr C++.
10
+ * Returns `{ code }` for statement ops.
11
+ */
12
+ export declare function lowerMatrix(op: HALOpIR): {
13
+ code?: string;
14
+ expression?: string;
15
+ };
@@ -0,0 +1,63 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Matrix lowering — gpio-kbd-matrix node + input-event trampoline
3
+ //
4
+ // The gpio-kbd-matrix driver (Zephyr input subsystem) scans the grid and
5
+ // reports each key as ABS_X (column) + ABS_Y (row) + BTN_TOUCH (press).
6
+ // The shim decodes the triple and calls the user's lowered callback as
7
+ // (row, col, pressed). The overlay generator synthesizes the DT node with
8
+ // row-gpios/col-gpios phandle arrays from the construction pad lists —
9
+ // the lowering's comment marker carries rows/cols to the toolchain scan
10
+ // (the pwm user-facts marker precedent).
11
+ // ----------------------------------------------------------------------------
12
+ /**
13
+ * Emit the matrix trampoline state. Called from shimLines when the program
14
+ * uses matrix.* — the listener targets the synthesized tc_matrix node, the
15
+ * handler pointer is assigned by the on_key lowering.
16
+ */
17
+ export function matrixInitLines() {
18
+ return [
19
+ '// CUTTLEFISH_MATRIX_BEGIN',
20
+ 'static void (*__tc_matrix_on_key)(double, double, bool) = NULL;',
21
+ 'static int32_t __tc_matrix_row = -1;',
22
+ 'static int32_t __tc_matrix_col = -1;',
23
+ 'static void __tc_matrix_cb(struct input_event* evt, void* user_data) {',
24
+ ' (void)user_data;',
25
+ ' switch (evt->code) {',
26
+ ' case INPUT_ABS_X:',
27
+ ' __tc_matrix_col = evt->value;',
28
+ ' break;',
29
+ ' case INPUT_ABS_Y:',
30
+ ' __tc_matrix_row = evt->value;',
31
+ ' break;',
32
+ ' case INPUT_BTN_TOUCH:',
33
+ ' if (__tc_matrix_on_key != NULL && __tc_matrix_row >= 0 && __tc_matrix_col >= 0) {',
34
+ ' __tc_matrix_on_key(static_cast<double>(__tc_matrix_row), static_cast<double>(__tc_matrix_col), evt->value != 0);',
35
+ ' }',
36
+ ' break;',
37
+ ' default:',
38
+ ' break;',
39
+ ' }',
40
+ '}',
41
+ 'INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_NODELABEL(tc_matrix)), __tc_matrix_cb, NULL);',
42
+ '// CUTTLEFISH_MATRIX_END',
43
+ ];
44
+ }
45
+ /**
46
+ * Resolve a HAL matrix.* op to Zephyr C++.
47
+ * Returns `{ code }` for statement ops.
48
+ */
49
+ export function lowerMatrix(op) {
50
+ const o = op;
51
+ switch (op.operation) {
52
+ case 'matrix.on_key': {
53
+ // The comment marker carries the construction pad lists to the
54
+ // toolchain's overlay scan (the pwm user-facts precedent).
55
+ return {
56
+ code: `/* cuttlefish-matrix: rows=${o.rows} cols=${o.cols} */ __tc_matrix_on_key = ${o.handler};`,
57
+ };
58
+ }
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
+ }
@@ -13,7 +13,11 @@
13
13
  // shim getaddrinfo-resolves the broker host itself (like the HTTP shim).
14
14
  // URI scheme: mqtt:// → 1883 plain TCP, mqtts:// → 8883 TLS
15
15
  // (MQTT_TRANSPORT_SECURE + mqtt_sec_config, reusing the same mbedTLS matrix
16
- // the HTTP TLS path established).
16
+ // the HTTP TLS path established). caCert pins the broker's CA — the PEM is
17
+ // DER-decoded at emit time and registered via tls_credential_add as a
18
+ // CA_CERTIFICATE sec tag with TLS_PEER_VERIFY_REQUIRED (verified TLS, the
19
+ // MQTT analogue of Request's caCert); mqtts:// without a CA stays
20
+ // encrypted-but-unverified (TLS_PEER_VERIFY_NONE).
17
21
  //
18
22
  // One client slot (HAL singleton). on_message callbacks are dispatched on the
19
23
  // poll thread.
@@ -45,6 +49,8 @@ export function mqttInitLines() {
45
49
  `// at connect time; host drives both DNS resolution and the TLS SNI/hostname.`,
46
50
  `// on_message is the user callback (set via mqtt.on_message); it fires on the`,
47
51
  `// poll thread for every incoming PUBLISH on a subscribed topic.`,
52
+ `// ca_der is the pinned CA (DER, user-owned from the caCert fact); nullptr`,
53
+ `// means mqtts:// runs encrypted-but-unverified.`,
48
54
  `typedef void (*__tc_mqtt_msg_cb_t)(const char* topic, const char* payload);`,
49
55
  `static struct {`,
50
56
  ` struct mqtt_client client;`,
@@ -55,6 +61,16 @@ export function mqttInitLines() {
55
61
  ` uint16_t port; // 1883 / 8883 unless overridden in the URI`,
56
62
  ` char client_id[64];`,
57
63
  ` bool tls; // scheme == "mqtts"`,
64
+ ` const uint8_t* ca_der; // pinned CA (DER), nullptr = no CA`,
65
+ ` size_t ca_der_len;`,
66
+ ` // What this shim last registered under __TC_MQTT_SEC_TAG (DER arrays`,
67
+ ` // are function-static, so pointer identity is stable). Zephyr's`,
68
+ ` // credential store never REPLACES a tag — repeats are skipped, a`,
69
+ ` // different CA at an occupied tag is reported, not silent.`,
70
+ ` const uint8_t* ca_added;`,
71
+ ` size_t ca_added_len;`,
72
+ ` bool ca_added_set;`,
73
+ ` sec_tag_t sec_tags[1]; // backs tls.config.sec_tag_list (static lifetime)`,
58
74
  ` volatile bool connected;`,
59
75
  ` volatile bool stop_poll; // set by disconnect to terminate the poll thread`,
60
76
  ` __tc_mqtt_msg_cb_t on_message;`,
@@ -196,6 +212,35 @@ export function mqttInitLines() {
196
212
  ` return host_len > 0U;`,
197
213
  `}`,
198
214
  ``,
215
+ `// Strict IPv4 dotted-quad predicate: exactly four 0-255 groups. A`,
216
+ `// digit-and-dot scan would also swallow numeric-only DNS names ("123"),`,
217
+ `// silently skipping the TLS hostname check for them (same helper as the`,
218
+ `// HTTP shim).`,
219
+ `static bool __tc_mqtt_host_is_ipv4(const char* h) {`,
220
+ ` uint32_t quads = 0U;`,
221
+ ` uint32_t val = 0U;`,
222
+ ` uint32_t digits = 0U;`,
223
+ ` for (const char* p = h; ; p++) {`,
224
+ ` if (*p >= '0' && *p <= '9') {`,
225
+ ` val = (val * 10U) + static_cast<uint32_t>(*p - '0');`,
226
+ ` digits++;`,
227
+ ` if (digits > 3U || val > 255U) return false;`,
228
+ ` } else if (*p == '.') {`,
229
+ ` if (digits == 0U || quads >= 3U) return false;`,
230
+ ` quads++;`,
231
+ ` val = 0U;`,
232
+ ` digits = 0U;`,
233
+ ` } else if (*p == '\\0') {`,
234
+ ` if (digits == 0U) return false;`,
235
+ ` quads++;`,
236
+ ` break;`,
237
+ ` } else {`,
238
+ ` return false;`,
239
+ ` }`,
240
+ ` }`,
241
+ ` return quads == 4U;`,
242
+ `}`,
243
+ ``,
199
244
  `// ── connect ─────────────────────────────────────────────────────────────`,
200
245
  `// Parse URI → resolve broker → init client → mqtt_connect → spawn poll thread.`,
201
246
  `// Blocks until CONNACK (the poll thread isn't running yet, so we drive a few`,
@@ -244,14 +289,47 @@ export function mqttInitLines() {
244
289
  ` __tc_mqtt.client.transport.type = MQTT_TRANSPORT_SECURE;`,
245
290
  ` struct mqtt_sec_config* tls_cfg = &__tc_mqtt.client.transport.tls.config;`,
246
291
  ` (void)memset(tls_cfg, 0, sizeof(*tls_cfg));`,
247
- ` // The HAL MQTT surface has no CA-pinning op (unlike HTTP's caCert), so`,
248
- ` // there is no way to register a trusted CA here. Verify-NONE keeps the`,
249
- ` // TLS session encrypted but skips identity verification — the strongest`,
250
- ` // the current surface can express. A future mqtt.set_ca_cert op would`,
251
- ` // flip this to REQUIRED + a registered sec_tag (like the HTTPS path).`,
252
- ` tls_cfg->peer_verify = TLS_PEER_VERIFY_NONE;`,
292
+ ` if (__tc_mqtt.ca_der != nullptr) {`,
293
+ ` // Pinned CA (the caCert construction fact): register it as a`,
294
+ ` // CA_CERTIFICATE sec tag and REQUIRE peer verification — the MQTT`,
295
+ ` // analogue of the HTTPS caCert path. Zephyr's credential store`,
296
+ ` // never REPLACES a tag+type pair: re-registering the same DER`,
297
+ ` // (pointer identity — the arrays are function-static) is skipped`,
298
+ ` // silently; a DIFFERENT CA hitting an occupied tag is reported so`,
299
+ ` // a wrong-CA verify is never silent; any other failure keeps the`,
300
+ ` // tag list empty and verification required — fail closed.`,
301
+ ` if (!__tc_mqtt.ca_added_set || __tc_mqtt.ca_added != __tc_mqtt.ca_der`,
302
+ ` || __tc_mqtt.ca_added_len != __tc_mqtt.ca_der_len) {`,
303
+ ` int cred_rc = tls_credential_add(__TC_MQTT_SEC_TAG,`,
304
+ ` TLS_CREDENTIAL_CA_CERTIFICATE,`,
305
+ ` __tc_mqtt.ca_der, __tc_mqtt.ca_der_len);`,
306
+ ` __tc_mqtt.ca_added = __tc_mqtt.ca_der;`,
307
+ ` __tc_mqtt.ca_added_len = __tc_mqtt.ca_der_len;`,
308
+ ` __tc_mqtt.ca_added_set = true;`,
309
+ ` if (cred_rc == -17 /* -EEXIST */) {`,
310
+ ` printk("tc-mqtt: sec tag %d already holds a CA; the first one stays active (rc=%d)\\n", __TC_MQTT_SEC_TAG, cred_rc);`,
311
+ ` } else if (cred_rc != 0) {`,
312
+ ` printk("tc-mqtt: CA registration failed (rc=%d) - verifying against no credentials\\n", cred_rc);`,
313
+ ` }`,
314
+ ` }`,
315
+ ` if (__tc_mqtt.ca_added_set) {`,
316
+ ` __tc_mqtt.sec_tags[0] = __TC_MQTT_SEC_TAG;`,
317
+ ` tls_cfg->sec_tag_list = __tc_mqtt.sec_tags;`,
318
+ ` tls_cfg->sec_tag_count = 1U;`,
319
+ ` }`,
320
+ ` tls_cfg->peer_verify = TLS_PEER_VERIFY_REQUIRED;`,
321
+ ` } else {`,
322
+ ` // No CA pinned: the session is encrypted but the broker's identity`,
323
+ ` // is not verified — the strongest the no-caCert form can express.`,
324
+ ` tls_cfg->peer_verify = TLS_PEER_VERIFY_NONE;`,
325
+ ` }`,
253
326
  ` tls_cfg->cipher_list = nullptr;`,
254
- ` tls_cfg->hostname = __tc_mqtt.host;`,
327
+ ` // mbedtls does not match IP literals against SAN entries — set the`,
328
+ ` // hostname (SNI + identity check) only for name hosts; IP brokers`,
329
+ ` // verify chain-only (same policy as the HTTPS path).`,
330
+ ` if (!__tc_mqtt_host_is_ipv4(__tc_mqtt.host)) {`,
331
+ ` tls_cfg->hostname = __tc_mqtt.host;`,
332
+ ` }`,
255
333
  `#else`,
256
334
  ` printk("tc-mqtt: mqtts:// requested but CONFIG_MQTT_LIB_TLS is off\\n");`,
257
335
  ` return;`,
@@ -303,6 +381,10 @@ export function mqttInitLines() {
303
381
  ` __tc_mqtt.on_message = fn;`,
304
382
  `}`,
305
383
  ``,
384
+ `// ca_cert is the DER-decoded PEM from the caCert construction fact`,
385
+ `// (user-owned static array; nullptr = no pinned CA).`,
386
+ `static inline void __tc_mqtt_set_ca_cert_der(const uint8_t* der, size_t len) { __tc_mqtt.ca_der = der; __tc_mqtt.ca_der_len = len; }`,
387
+ ``,
306
388
  `static inline void __tc_mqtt_subscribe(const char* topic) {`,
307
389
  ` static struct mqtt_topic topics[4];`,
308
390
  ` static uint16_t msg_id = 1;`,
@@ -356,6 +438,26 @@ export function lowerMqtt(op) {
356
438
  switch (op.operation) {
357
439
  case 'mqtt.connect':
358
440
  return { code: `__tc_mqtt_connect(${s(o.brokerUri)}, ${s(o.clientId)});` };
441
+ case 'mqtt.set_ca_cert': {
442
+ // PEM → DER at emit time (this tree's tf-psa-crypto mbedTLS has no PEM
443
+ // parser — same decode as http.set_ca_cert; tls_credential_add takes
444
+ // the DER directly).
445
+ const pem = String(o.pem ?? '').replace(/^"|"$/g, '').replace(/\\n/g, '\n');
446
+ if (pem.replace(/\s+/g, '') === '') {
447
+ // No-CA sentinel (Mqtt always emits the op from connect(); opts.caCert
448
+ // defaults to '') — elide silently so plain-mqtt:// carries no comment.
449
+ return { code: '' };
450
+ }
451
+ const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s+/g, '');
452
+ const der = Buffer.from(b64, 'base64');
453
+ // Every X.509 DER starts with the ASN.1 SEQUENCE tag (0x30); a long
454
+ // but non-cert payload fails here instead of at a confusing handshake
455
+ // error on the target (same check as http.set_ca_cert).
456
+ if (der.length < 100 || der[0] !== 0x30)
457
+ return { code: `// tc-mqtt: caCert PEM failed to decode` };
458
+ const hex = [...der].map((b) => `0x${b.toString(16).padStart(2, '0')}`).join(', ');
459
+ return { code: `{ static const uint8_t __tc_mqtt_ca_der[] = { ${hex} }; __tc_mqtt_set_ca_cert_der(__tc_mqtt_ca_der, sizeof(__tc_mqtt_ca_der)); }` };
460
+ }
359
461
  case 'mqtt.on_message':
360
462
  return { code: `__tc_mqtt_set_on_message(${s(o.handler)});` };
361
463
  case 'mqtt.subscribe':
@@ -1,9 +1,10 @@
1
1
  import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
2
3
  /**
3
4
  * Resolve a HAL power.* op to Zephyr C++.
4
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
5
+ * Returns `{ code }` for statement ops.
5
6
  */
6
- export declare function lowerPower(op: HALOpIR): {
7
+ export declare function lowerPower(op: HALOpIR, chip?: ZephyrChipDescriptor): {
7
8
  code?: string;
8
9
  expression?: string;
9
10
  };
@@ -1,56 +1,31 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Power lowering — Zephyr power management
2
+ // Power lowering — sys_poweroff() for explicit soft-off entry
3
3
  //
4
- // Zephyr enters low-power states from the idle thread per policy. The app-facing
5
- // controls are pm_state_force (override policy to force a state on next idle)
6
- // and k_sleep (block the calling thread, allowing the system to idle into a
7
- // low-power state). "Deep sleep" maps to k_sleep (the SoC picks its deepest
8
- // allowed state); "light sleep" best-effort forces SUSPEND_TO_IDLE.
9
- //
10
- // deep_sleep_pin configures the wake GPIO as an interrupt source then k_sleeps
11
- // (the interrupt wakes the SoC). CPU frequency is SoC-specific (nRF52 has a
12
- // fixed 64 MHz HFXO with no portable dynamic scaling) and stays a documented
13
- // no-op. deep_sleep_pin targets gpio0 (single-controller nRF52840 case);
14
- // multi-controller SoC routing is a documented follow-on.
15
- // ---------------------------------------------------------------------------
4
+ // Both SoC families implement the kernel's sys_poweroff(): ESP32 parks the
5
+ // RTC domain and calls esp_deep_sleep_start(); STM32 enters standby-class
6
+ // deep sleep. The call never returns — wake is a reset or a wake source
7
+ // (board-specific: the Black Pill's WKUP pin is PA0; ESP32 GPIOs armed as
8
+ // wake triggers). Statement emission continues after the call site, but
9
+ // nothing there runs — the transpiler treats it as any other void call.
10
+ // ----------------------------------------------------------------------------
16
11
  /**
17
12
  * Resolve a HAL power.* op to Zephyr C++.
18
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
13
+ * Returns `{ code }` for statement ops.
19
14
  */
20
- export function lowerPower(op) {
15
+ export function lowerPower(op, chip) {
21
16
  const o = op;
22
17
  switch (op.operation) {
23
- case 'power.deep_sleep':
24
- // Block for the requested duration; the idle thread + PM policy will enter
25
- // the deepest state the SoC allows during the sleep.
26
- return { code: `k_sleep(K_MSEC(${o.ms}));` };
27
- case 'power.light_sleep':
28
- // Best-effort: force a shallow suspend on the next idle. pm_state_force
29
- // requires a pm_state_info; SUSPEND_TO_IDLE is a common shallow state.
30
- return { code: `pm_state_force(0, &(const struct pm_state_info){ .state = PM_STATE_SUSPEND_TO_IDLE, .substate_id = 0 });` };
31
- case 'power.deep_sleep_pin': {
32
- // Configure the pin as a GPIO wake source (level interrupt), then k_sleep
33
- // until the interrupt fires. A static gpio_callback + a no-op handler (the
34
- // wake just needs the interrupt enabled; the handler does nothing). level:
35
- // 1=high, 0=low. Targets gpio0 (single-controller nRF52840); multi-
36
- // controller SoC routing is a documented follow-on.
37
- const pin = o.pin;
38
- const level = o.level;
39
- const flags = level ? 'GPIO_INT_LEVEL_HIGH' : 'GPIO_INT_LEVEL_LOW';
18
+ case 'power.off':
19
+ return { code: 'sys_poweroff();' };
20
+ case 'power.off_for': {
21
+ // DAC discipline: without the RTC wake-timer fact, a timed off would
22
+ // sleep with no wake source — lower to a comment naming it.
23
+ if (!chip?.powerWakeTimer) {
24
+ return { code: `/* Power.offFor(${o.ms} ms): this board's SoC declares no RTC wake timer — soft-off here has no armed wake; use Power.off() */` };
25
+ }
26
+ // Arm the RTC timer (µs), then enter soft-off; wake is a reboot.
40
27
  return {
41
- code: [
42
- '{',
43
- ` static struct gpio_callback __tc_wake_cb;`,
44
- ` const struct device* __dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));`,
45
- ` gpio_pin_configure(__dev, ${pin}, GPIO_INPUT);`,
46
- ` gpio_init_callback(&__tc_wake_cb, [](const struct device*, struct gpio_callback*, gpio_port_pins_t) { (void)0; }, BIT(${pin}));`,
47
- ` gpio_add_callback(__dev, &__tc_wake_cb);`,
48
- ` gpio_pin_interrupt_configure(__dev, ${pin}, ${flags});`,
49
- ` k_sleep(K_FOREVER);`,
50
- ` gpio_pin_interrupt_configure(__dev, ${pin}, GPIO_INT_DISABLE);`,
51
- ` gpio_remove_callback(__dev, &__tc_wake_cb);`,
52
- '}',
53
- ].join(' '),
28
+ code: `{ esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(${o.ms}) * 1000ULL); sys_poweroff(); }`,
54
29
  };
55
30
  }
56
31
  default:
@@ -140,6 +140,31 @@ export function lowerPwm(op, chip) {
140
140
  // pulse to idle — follow with setPulse/setDuty to drive the line.
141
141
  return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
142
142
  }
143
+ // ── Servo (hal/servo.ts) — calibrated 50 Hz sugar on the same channel ──
144
+ // The commanded value is runtime text; the calibrated range and travel
145
+ // are construction literals carried on the op. Clamping and the angle→
146
+ // pulse mapping run in the emitted C++ — they cannot fold at transpile
147
+ // time. Pulse widths convert µs→ns here (×1000).
148
+ case 'pwm.servo_us': {
149
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
150
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
151
+ return {
152
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } uint32_t __tc_sv_p${o.pin} = static_cast<uint32_t>(${o.us}) * 1000U; if (__tc_sv_p${o.pin} < ${minNs}U) { __tc_sv_p${o.pin} = ${minNs}U; } if (__tc_sv_p${o.pin} > ${maxNs}U) { __tc_sv_p${o.pin} = ${maxNs}U; } (void)pwm_set_pulse_dt(&${v}, __tc_sv_p${o.pin}); }`,
153
+ };
154
+ }
155
+ case 'pwm.servo_angle': {
156
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
157
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
158
+ const maxAngle = Number(o.maxAngle ?? 180) || 180;
159
+ return {
160
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } double __tc_sv_a${o.pin} = static_cast<double>(${o.angle}); if (__tc_sv_a${o.pin} < 0.0) { __tc_sv_a${o.pin} = 0.0; } if (__tc_sv_a${o.pin} > ${maxAngle}.0) { __tc_sv_a${o.pin} = ${maxAngle}.0; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${minNs}) + (__tc_sv_a${o.pin} * ((static_cast<double>(${maxNs}) - static_cast<double>(${minNs})) / static_cast<double>(${maxAngle}.0))))); }`,
161
+ };
162
+ }
163
+ case 'pwm.servo_idle': {
164
+ return {
165
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, 0U); }`,
166
+ };
167
+ }
143
168
  default:
144
169
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
145
170
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -17,7 +17,7 @@ export interface SensorNames {
17
17
  /** The bus port: I2C address or SPI CS pin. */
18
18
  port: number;
19
19
  /** 'i2c' | 'spi'. */
20
- busKind: 'i2c' | 'spi';
20
+ busKind: 'i2c' | 'spi' | 'w1';
21
21
  }
22
22
  /** Derive a sensor's DT/C++ names. The scanner regexes the emitted __tc_
23
23
  * prefix form, so the part group must stay [a-z0-9_]+ and greedy. */
@@ -26,7 +26,7 @@ export declare function sensorNames(part: string, bus: string, port: number | st
26
26
  * Emit the per-sensor state block (device handle + sensor_value scratch).
27
27
  * Called from shimLines for each distinct sensor the program's ops reference.
28
28
  */
29
- export declare function sensorStateLines(part: string, bus: string, port: number | string, busKind?: string, spiHz?: number | string, spiMode?: number | string, alertPin?: number | string): string[];
29
+ export declare function sensorStateLines(part: string, bus: string, port: number | string, busKind?: string, spiHz?: number | string, spiMode?: number | string, alertPin?: number | string, resolution?: number | string): string[];
30
30
  /**
31
31
  * Resolve a HAL sensor.* op to Zephyr C++.
32
32
  * Returns `{ code }` for fetch (statement), `{ expression }` for get (value).
@@ -31,14 +31,16 @@ export function sensorNames(part, bus, port, busKind = 'i2c') {
31
31
  const portNum = typeof port === 'number' ? port : parseInt(String(port));
32
32
  const stem = busKind === 'spi'
33
33
  ? `${partKey}_spi${busIndex}_cs${portNum}`
34
- : `${partKey}_i2c${busIndex}_0x${portNum.toString(16)}`;
34
+ : busKind === 'w1'
35
+ ? `${partKey}_w1_p${portNum}`
36
+ : `${partKey}_i2c${busIndex}_0x${portNum.toString(16)}`;
35
37
  return {
36
38
  dtLabel: `tc_${stem}`,
37
39
  devVar: `__tc_sensor_${stem}_dev`,
38
40
  valVar: `__tc_sensor_${stem}_val`,
39
41
  busIndex,
40
42
  port: portNum,
41
- busKind: busKind === 'spi' ? 'spi' : 'i2c',
43
+ busKind: busKind === 'spi' ? 'spi' : busKind === 'w1' ? 'w1' : 'i2c',
42
44
  };
43
45
  }
44
46
  /**
@@ -67,13 +69,15 @@ function requireBusKind(partKey, info, busKind) {
67
69
  * Emit the per-sensor state block (device handle + sensor_value scratch).
68
70
  * Called from shimLines for each distinct sensor the program's ops reference.
69
71
  */
70
- export function sensorStateLines(part, bus, port, busKind = 'i2c', spiHz = 0, spiMode = 0, alertPin = -1) {
72
+ export function sensorStateLines(part, bus, port, busKind = 'i2c', spiHz = 0, spiMode = 0, alertPin = -1, resolution = 12) {
71
73
  const partKey = sensorPartKey(String(part));
72
74
  requireBusKind(partKey, requirePartInfo(partKey), busKind);
73
75
  const n = sensorNames(part, bus, port, busKind);
74
76
  // The config comment carries construction facts (SPI clock, mode, alert
75
77
  // GPIO) to the overlay scanner — they shape the DT node, not the C++.
76
- const cfg = `// tc-sensor-cfg: ${n.dtLabel} hz=${Number(spiHz)} mode=${Number(spiMode)} alert=${Number(alertPin)}`;
78
+ const cfg = busKind === 'w1'
79
+ ? `// tc-sensor-cfg: ${n.dtLabel} res=${Number(resolution)}`
80
+ : `// tc-sensor-cfg: ${n.dtLabel} hz=${Number(spiHz)} mode=${Number(spiMode)} alert=${Number(alertPin)}`;
77
81
  return [
78
82
  '// CUTTLEFISH_SENSOR_BEGIN',
79
83
  `static const struct device* ${n.devVar} = DEVICE_DT_GET(DT_NODELABEL(${n.dtLabel}));`,