@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,545 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Zephyr framework manifest
3
+ //
4
+ // Coverage reflects actual resolveHALOperation / lowerHalOp + resolveDisplayOp
5
+ // behavior. GPIO, PWM, ADC, I2C, SPI, UART, interrupts, tone, power, pulse,
6
+ // shift, WDT, BLE, timing, WiFi, HTTP/S, MQTT, board constants, and random
7
+ // are lowered; display is lowered via the generic <zephyr/drivers/display.h>
8
+ // GFX runtime. WiFi/HTTP/MQTT require an ESP32 target (nRF52840 has no radio).
9
+ // The manifest validator probes every declared op against the resolver: a
10
+ // 'supported' op must lower, an 'unsupported' op must return undefined.
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import { defineFrameworkManifest, HAL_OPERATION_KINDS } from '@typecad/cuttlefish/api/shared';
14
+
15
+ /**
16
+ * Build an `{ opKind: 'unsupported' }` record for every op kind under a given
17
+ * category prefix. Keeps unsupported categories honest without hand-listing
18
+ * every op (the validator requires every op kind be declared, even as
19
+ * unsupported). Mirrors the discipline esp32 applies by hand-listing.
20
+ */
21
+ function unsupportedOps(prefix: string): Record<string, 'unsupported'> {
22
+ const out: Record<string, 'unsupported'> = {};
23
+ for (const kind of HAL_OPERATION_KINDS) {
24
+ if (kind.startsWith(prefix)) out[kind] = 'unsupported';
25
+ }
26
+ return out;
27
+ }
28
+
29
+ export default defineFrameworkManifest({
30
+ schemaVersion: 1,
31
+ frameworkId: 'zephyr',
32
+ packageName: '@typecad/framework-zephyr',
33
+ canonical: false,
34
+ displayName: 'Zephyr RTOS',
35
+ description:
36
+ 'Native Zephyr framework targeting the Zephyr RTOS via west/CMake. ' +
37
+ 'GPIO is lowered through devicetree specs (gpio_pin_*_dt).',
38
+ implementationMode: 'from-scratch',
39
+
40
+ entrypoint: {
41
+ entrypointFunctionName: 'setup',
42
+ requiresLoopFunction: true,
43
+ sourceExtension: 'cpp',
44
+ overrideBaseName: 'main',
45
+ outputSubdirectory: 'src',
46
+ generateHeaderFile: true,
47
+ customBridgeShim: 'main',
48
+ },
49
+
50
+ profile: {
51
+ // Informational list of supported board targets. The manifest validator
52
+ // never iterates this; chipForTarget (src/chips/index.ts) is the real
53
+ // resolver. ESP32-S3 + plain ESP32 added alongside the nRF52840 MVP target.
54
+ targets: ['xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc'],
55
+ forcedIncludes: ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'],
56
+ symbolAliases: {},
57
+ },
58
+
59
+ hal: {
60
+ // ── Supported: GPIO (devicetree-spec bridge) ──────────────────────────
61
+ gpio: {
62
+ supported: true,
63
+ partialCoverage: false,
64
+ ops: {
65
+ 'gpio.write': 'supported',
66
+ 'gpio.read': 'supported',
67
+ 'gpio.toggle': 'supported',
68
+ 'gpio.set_mode': 'supported',
69
+ },
70
+ },
71
+
72
+ // ── Supported: Timing (delay/millis/delay_us/micros/free_heap; timers via polyfill) ─
73
+ timing: {
74
+ supported: true,
75
+ partialCoverage: false,
76
+ ops: {
77
+ 'timing.delay': 'supported', // → k_msleep(ms)
78
+ 'timing.delay_microseconds': 'supported', // → k_busy_wait(us)
79
+ 'timing.millis': 'supported', // → k_uptime_get_32()
80
+ 'timing.micros': 'supported', // → k_cycle_get_32 + cycles/sec
81
+ 'timing.free_heap': 'supported', // → 0 (no portable query; see lowering)
82
+ // Timer ops are POLYFILL_BACKED_OPS → timer_methods. The validator skips
83
+ // the resolver probe (these legitimately return polyfill-helper calls,
84
+ // not direct lowering) and requires the polyfill be in polyfills.emitted.
85
+ 'timing.set_interval': 'polyfill',
86
+ 'timing.set_timeout': 'polyfill',
87
+ 'timing.clear_interval': 'polyfill',
88
+ 'timing.clear_timeout': 'polyfill',
89
+ },
90
+ },
91
+
92
+ // ── Supported: PWM (pwm_dt_spec via pwm-led0) ──────────────────────────
93
+ pwm: {
94
+ supported: true,
95
+ partialCoverage: false,
96
+ ops: {
97
+ 'pwm.write': 'supported',
98
+ 'pwm.get_frequency': 'supported',
99
+ 'pwm.get_resolution': 'supported',
100
+ },
101
+ },
102
+
103
+ // ── Supported: ADC (SAADC via adc_read + channel setup) ────────────────
104
+ adc: {
105
+ supported: true,
106
+ partialCoverage: false,
107
+ ops: {
108
+ 'adc.read': 'supported',
109
+ 'adc.get_resolution': 'supported',
110
+ 'adc.set_reference': 'supported',
111
+ 'adc.get_reference': 'supported',
112
+ 'adc.read_voltage': 'supported',
113
+ },
114
+ },
115
+ dac: {
116
+ supported: false,
117
+ unsupportedReason: 'No DAC lowering implemented in the framework (not applicable on nRF52840; ESP32 variants with DAC not yet wired).',
118
+ ops: { 'dac.write': 'unsupported' },
119
+ },
120
+ interrupts: {
121
+ supported: true,
122
+ partialCoverage: true,
123
+ unsupportedReason: undefined,
124
+ ops: {
125
+ 'interrupt.attach': 'supported',
126
+ 'interrupt.detach': 'supported',
127
+ },
128
+ },
129
+ tone: {
130
+ supported: true,
131
+ partialCoverage: true,
132
+ ops: {
133
+ 'tone.play': 'supported', // PWM-based square wave (blocking for duration)
134
+ 'tone.stop': 'supported',
135
+ },
136
+ },
137
+ power: {
138
+ supported: true,
139
+ partialCoverage: true,
140
+ ops: {
141
+ 'power.deep_sleep': 'supported', // k_sleep (deepest allowed state)
142
+ 'power.light_sleep': 'supported', // pm_state_force SUSPEND_TO_IDLE
143
+ 'power.set_cpu_frequency': 'supported', // comment (nRF clock API deferred)
144
+ 'power.deep_sleep_pin': 'supported', // GPIO-interrupt wake + k_sleep
145
+ },
146
+ },
147
+ i2c: {
148
+ supported: true,
149
+ partialCoverage: false,
150
+ ops: {
151
+ 'i2c.begin': 'supported', 'i2c.end': 'supported', 'i2c.set_clock': 'supported',
152
+ 'i2c.begin_transmission': 'supported', 'i2c.write': 'supported',
153
+ 'i2c.write_bytes': 'supported', 'i2c.write_buffer': 'supported',
154
+ 'i2c.read_buffer': 'supported', 'i2c.end_transmission': 'supported',
155
+ 'i2c.request_from': 'supported', 'i2c.available': 'supported', 'i2c.read': 'supported',
156
+ 'i2c.recover': 'supported',
157
+ },
158
+ },
159
+ spi: {
160
+ supported: true,
161
+ partialCoverage: false,
162
+ ops: {
163
+ 'spi.begin': 'supported', 'spi.end': 'supported', 'spi.transfer': 'supported',
164
+ 'spi.begin_transaction': 'supported', 'spi.end_transaction': 'supported',
165
+ 'spi.set_mode': 'supported', 'spi.set_bit_order': 'supported',
166
+ 'spi.cs_low': 'supported', 'spi.cs_high': 'supported', 'spi.read_buffer': 'supported',
167
+ },
168
+ },
169
+ uart: {
170
+ supported: true,
171
+ partialCoverage: true,
172
+ ops: {
173
+ 'uart.begin': 'supported', 'uart.end': 'supported', 'uart.print': 'supported',
174
+ 'uart.println': 'supported', 'uart.printf': 'supported', 'uart.write': 'supported',
175
+ 'uart.read': 'supported', 'uart.peek': 'supported', 'uart.available': 'supported',
176
+ 'uart.flush': 'supported',
177
+ },
178
+ },
179
+ pulse: {
180
+ supported: true,
181
+ partialCoverage: true,
182
+ ops: { 'pulse.in': 'supported', 'pulse.in_long': 'supported' },
183
+ },
184
+ shift: {
185
+ supported: true,
186
+ partialCoverage: false,
187
+ ops: { 'shift.out': 'supported', 'shift.in': 'supported' },
188
+ },
189
+ board: {
190
+ // Board constant resolution. Board.definition.<path> /
191
+ // Pins.definition.<path> property accesses are folded by
192
+ // ZephyrStrategy.renderBoardDefinitionAccess against the loaded
193
+ // board/MCU constants — the same mechanism framework-arduino uses. The
194
+ // lone HAL op (board.resolve) is constant-folded at IR-build time
195
+ // (expression-to-ir.ts / hal-emitter.ts), so it only reaches the
196
+ // resolver as a dead-letter; 'probe-inconclusive' reflects that the
197
+ // minimal validator probe carries no path/board constants to resolve.
198
+ supported: true,
199
+ partialCoverage: false,
200
+ ops: { 'board.resolve': 'probe-inconclusive' },
201
+ },
202
+ wdt: {
203
+ supported: true,
204
+ partialCoverage: false,
205
+ ops: {
206
+ 'wdt.enable': 'supported',
207
+ 'wdt.reset': 'supported',
208
+ 'wdt.disable': 'supported',
209
+ },
210
+ },
211
+ // ── Partial: WiFi (STA connect + scan + config via conn_mgr/net_mgmt) ────
212
+ // ESP32-S3 only — profileDiagnostics flags wifi usage on radioless chips.
213
+ // AP mode, credential persistence, static IP, and event callbacks deferred.
214
+ wifi: {
215
+ supported: true,
216
+ partialCoverage: true,
217
+ // Unsupported surface: AP client enumeration/IP/per-station-config have no
218
+ // driver hook; credentials need a custom settings-subsystem layer; static IP
219
+ // / auto-reconnect / tx-power aren't wifi-shaped or aren't exposed by the
220
+ // esp32 Zephyr driver. See per-op reasons.
221
+ unsupportedReason: 'AP client enumeration/IP/per-station config, credential persistence, static IP, auto-reconnect, and tx-power have no Zephyr lowering (no driver/Kconfig hook).',
222
+ ops: {
223
+ // Connection (8) — net_mgmt connect/disconnect + L4 connectivity state.
224
+ 'wifi.connect': 'supported', 'wifi.connect_start': 'supported',
225
+ 'wifi.disconnect': 'supported', 'wifi.status': 'supported',
226
+ 'wifi.is_connected': 'supported', 'wifi.local_ip': 'supported',
227
+ 'wifi.rssi': 'supported', 'wifi.mac': 'supported',
228
+ // Waits (2) — block on the L4 connected flag (k_msleep poll).
229
+ 'wifi.wait_connected': 'supported', 'wifi.wait_disconnected': 'supported',
230
+ // Scan (8) — net_mgmt NET_REQUEST_WIFI_SCAN + result pool.
231
+ 'wifi.scan': 'supported', 'wifi.scan_start': 'supported',
232
+ 'wifi.scan_done': 'supported', 'wifi.scan_count': 'supported',
233
+ 'wifi.scan_ssid': 'supported', 'wifi.scan_rssi': 'supported',
234
+ 'wifi.scan_encryption': 'supported', 'wifi.scan_channel': 'supported',
235
+ // AP mode (2 of 7) — esp32 driver wires ap_enable/ap_disable. Only
236
+ // ssid/password/channel are honored; the rest of the AP surface has no
237
+ // driver hook (config_params isn't wired, max_clients hardcodes 5).
238
+ 'wifi.ap_start': 'supported', 'wifi.ap_stop': 'supported',
239
+ // Config (2) — hostname (best-effort no-op) + power save (real net_mgmt).
240
+ 'wifi.set_hostname': 'supported', 'wifi.set_power_save': 'supported',
241
+ // on_event: 'disconnect' (NET_EVENT_L4_DISCONNECTED) + 'connect'
242
+ // (NET_EVENT_IPV4_ADDR_ADD) are lowered.
243
+ 'wifi.on_event': 'supported',
244
+ // ── Genuinely unsupported (no Zephyr/driver hook) ───────────────────
245
+ // ap_client_count: no API to enumerate connected AP stations.
246
+ // ap_ip: set via net_if, not wifi net_mgmt.
247
+ // ap_set_channel: channel set at ap_start; driver doesn't wire ap_config_params.
248
+ // ap_set_hidden: esp32 ap_enable config has no ssid_hidden field.
249
+ // ap_set_max_clients: driver hardcodes max_connection to 5; config_params unwired.
250
+ // save_credentials/connect_saved/clear_credentials: no wifi-credentials API in
251
+ // Zephyr (would need a custom settings-subsystem layer).
252
+ // set_static_ip: a net_if operation, not a wifi net_mgmt request.
253
+ // set_auto_reconnect: esp32 driver doesn't expose esp_wifi_set_auto_connect.
254
+ // set_tx_power: driver owns the radio; the PHY ceiling isn't a Zephyr Kconfig (zephyr#45580).
255
+ 'wifi.ap_client_count': 'unsupported',
256
+ 'wifi.ap_ip': 'unsupported',
257
+ 'wifi.ap_set_channel': 'unsupported',
258
+ 'wifi.ap_set_hidden': 'unsupported',
259
+ 'wifi.ap_set_max_clients': 'unsupported',
260
+ 'wifi.save_credentials': 'unsupported', 'wifi.connect_saved': 'unsupported',
261
+ 'wifi.clear_credentials': 'unsupported',
262
+ 'wifi.set_static_ip': 'unsupported',
263
+ 'wifi.set_auto_reconnect': 'unsupported',
264
+ 'wifi.set_tx_power': 'unsupported',
265
+ },
266
+ },
267
+ http: {
268
+ supported: true,
269
+ partialCoverage: false,
270
+ // HTTP/S client over Zephyr BSD sockets + http_client_req (TLS via
271
+ // mbedTLS / NET_SOCKETS_SOCKOPT_TLS). The __tc_http shim owns url parse,
272
+ // DNS (getaddrinfo), socket/TLS connect, and body accumulation. Requires
273
+ // a networked target (ESP32 WiFi); profileDiagnostics flags usage on a
274
+ // radioless chip as 'zephyr-http-unavailable-on-target'.
275
+ ops: {
276
+ 'http.begin': 'supported', 'http.reset': 'supported',
277
+ 'http.set_header': 'supported', 'http.set_timeout': 'supported',
278
+ 'http.set_max_body': 'supported', 'http.set_body': 'supported',
279
+ 'http.set_insecure': 'supported', 'http.set_ca_cert': 'supported',
280
+ 'http.send': 'supported', 'http.send_start': 'supported',
281
+ 'http.done': 'supported', 'http.status': 'supported',
282
+ 'http.ok': 'supported', 'http.body': 'supported',
283
+ 'http.content_length': 'supported', 'http.response_header': 'supported',
284
+ },
285
+ },
286
+ display: {
287
+ supported: true,
288
+ partialCoverage: false,
289
+ unsupportedReason: undefined,
290
+ drivers: ['ili9341-zephyr', 'st7796-zephyr'],
291
+ colorFormat: 'rgb565',
292
+ ops: {
293
+ 'display.init': 'supported',
294
+ 'display.fill_rect': 'supported',
295
+ 'display.draw_text': 'supported',
296
+ 'display.draw_rect': 'supported',
297
+ 'display.flush': 'supported',
298
+ },
299
+ },
300
+ // ── Supported: BLE (NimBLE GATT peripheral via runtime service register) ──
301
+ ble: {
302
+ supported: true,
303
+ partialCoverage: true,
304
+ ops: Object.fromEntries(
305
+ HAL_OPERATION_KINDS.filter((k) => k.startsWith('ble.')).map((k) => [k, 'supported']),
306
+ ),
307
+ },
308
+
309
+ // ── Supported: Worker offload (k_work system workqueue + k_sem) ──────────
310
+ // worker.* is lowered via lowerWorkerOp against the shared __tc_worker
311
+ // contract; the Zephyr backing (worker-backing.ts) supplies k_work + k_sem.
312
+ worker: {
313
+ supported: true,
314
+ partialCoverage: false,
315
+ ops: { 'worker.submit': 'supported', 'worker.done': 'supported' },
316
+ },
317
+
318
+ // ── Honestly unsupported extended categories ─────────────────────────────
319
+ // These have op-kinds in HAL_OPERATION_KINDS but no Zephyr lowering. Each
320
+ // is declared unsupported (with a reason) so the coverage matrix is uniform
321
+ // and the resolver's `return undefined` for these prefixes is honest. The
322
+ // catchall schema (HalCoverageSchema) validates any declared extended
323
+ // category; declaring them keeps the manifest a complete coverage record.
324
+
325
+ rmt: {
326
+ supported: false,
327
+ unsupportedReason: 'RMT (ESP32 infrared/transaction peripheral) has no Zephyr lowering.',
328
+ partialCoverage: false,
329
+ ops: unsupportedOps('rmt.'),
330
+ },
331
+ snprintf: {
332
+ supported: false,
333
+ unsupportedReason: 'snprintf.emit is a raw escape hatch; the Zephyr resolver returns undefined (use rawCpp()).',
334
+ partialCoverage: false,
335
+ ops: { 'snprintf.emit': 'unsupported' },
336
+ },
337
+ preferences: {
338
+ // ZMS-backed Zephyr settings. The HAL ESP32-NVS session model (begin/end
339
+ // + typed put/get) is modeled on top of Zephyr's flat settings key-space:
340
+ // begin(ns) records a "tc/<ns>/" prefix; put/get operate on an in-RAM
341
+ // cache populated once at boot by settings_load()'s h_set callback;
342
+ // writes mirror to flash via settings_save_one/settings_delete. The ZMS
343
+ // backend auto-locates the storage_partition fixed-partition (or the
344
+ // /chosen zephyr,settings-partition node — see dt-config/overlay.ts).
345
+ // begin/end are no-ops beyond prefix bookkeeping: Zephyr settings has no
346
+ // session/namespace, but keeping the ops preserves portability with the
347
+ // ESP32 NVS model and leaves a hook for a future session-needing backend.
348
+ supported: true,
349
+ partialCoverage: false,
350
+ ops: {
351
+ 'preferences.begin': 'supported', 'preferences.end': 'supported',
352
+ 'preferences.clear': 'supported', 'preferences.remove': 'supported',
353
+ 'preferences.put_int': 'supported', 'preferences.get_int': 'supported',
354
+ 'preferences.put_uint': 'supported', 'preferences.get_uint': 'supported',
355
+ 'preferences.put_bool': 'supported', 'preferences.get_bool': 'supported',
356
+ 'preferences.put_float': 'supported', 'preferences.get_float': 'supported',
357
+ 'preferences.put_string': 'supported', 'preferences.get_string': 'supported',
358
+ },
359
+ },
360
+ random: {
361
+ // <zephyr/random/random.h> sys_rand_get seeds a userspace xorshift32
362
+ // PRNG (__tc_rand_*); random.seed re-seeds it deterministically (matching
363
+ // Arduino randomSeed). random.int → [0, 2^31-1], random.range → [min,max-1].
364
+ supported: true,
365
+ partialCoverage: false,
366
+ ops: {
367
+ 'random.int': 'supported',
368
+ 'random.range': 'supported',
369
+ 'random.seed': 'supported',
370
+ },
371
+ },
372
+ fs: {
373
+ supported: false,
374
+ unsupportedReason: 'No filesystem lowering on Zephyr (Zephyr has its own FS API; not wired).',
375
+ partialCoverage: false,
376
+ ops: unsupportedOps('fs.'),
377
+ },
378
+ mdns: {
379
+ supported: false,
380
+ unsupportedReason: 'No mDNS lowering on Zephyr (requires networking stack).',
381
+ partialCoverage: false,
382
+ ops: unsupportedOps('mdns.'),
383
+ },
384
+ mqtt: {
385
+ supported: true,
386
+ partialCoverage: false,
387
+ // MQTT 3.1.1 client over Zephyr <zephyr/net/mqtt.h> (mqtts:// TLS via
388
+ // MQTT_TRANSPORT_SECURE + the shared mbedTLS matrix, encryption only — the
389
+ // HAL surface has no CA-pinning op, so peer verify is NONE). The __tc_mqtt
390
+ // shim resolves the broker, runs the mqtt_input/mqtt_live poll loop on a
391
+ // background k_thread, and dispatches incoming PUBLISHes to the user's
392
+ // onMessage callback. Requires a networked target (ESP32 WiFi);
393
+ // profileDiagnostics flags usage on a radioless chip.
394
+ ops: {
395
+ 'mqtt.connect': 'supported', 'mqtt.on_message': 'supported',
396
+ 'mqtt.subscribe': 'supported', 'mqtt.publish': 'supported',
397
+ 'mqtt.connected': 'supported', 'mqtt.disconnect': 'supported',
398
+ },
399
+ },
400
+ ota: {
401
+ supported: false,
402
+ unsupportedReason: 'No OTA lowering on Zephyr (Zephyr has MCUmgr; not wired).',
403
+ partialCoverage: false,
404
+ ops: unsupportedOps('ota.'),
405
+ },
406
+ temp: {
407
+ supported: false,
408
+ unsupportedReason: 'No on-chip temperature lowering on Zephyr (nRF52840 TEMP peripheral; not wired).',
409
+ partialCoverage: false,
410
+ ops: { 'temp.read': 'unsupported' },
411
+ },
412
+ hwtimer: {
413
+ supported: false,
414
+ unsupportedReason: 'No hardware-timer lowering on Zephyr (timers are handled via the k_timer polyfill, not hwtimer.*).',
415
+ partialCoverage: false,
416
+ ops: unsupportedOps('hwtimer.'),
417
+ },
418
+ capacitive: {
419
+ // FT6336U capacitive touch is handled via the strategy-owned touch adapter
420
+ // (src/display/touch-adapter.ts → touch_init/touch_isTouched/touch_readRaw),
421
+ // NOT via a HAL op lowering. The manifest marks it unsupported here (no
422
+ // resolveHALOperation path for capacitive.read); the touch adapter provides
423
+ // the integration. partialCoverage reflects that it works on targets with
424
+ // the FT6336U DT node (ESP32), not on the bare nRF52840.
425
+ supported: false,
426
+ unsupportedReason: 'Capacitive touch is handled via the strategy touch adapter (FT6336U I2C), not a HAL op. Requires the ft6336u DT node.',
427
+ partialCoverage: true,
428
+ ops: { 'capacitive.read': 'unsupported' },
429
+ },
430
+ i2s: {
431
+ supported: false,
432
+ unsupportedReason: 'No I2S / digital audio lowering on Zephyr.',
433
+ partialCoverage: false,
434
+ ops: unsupportedOps('i2s.'),
435
+ },
436
+ twai: {
437
+ supported: false,
438
+ unsupportedReason: 'No CAN / TWAI lowering on Zephyr (Zephyr CAN driver not wired).',
439
+ partialCoverage: false,
440
+ ops: unsupportedOps('twai.'),
441
+ },
442
+ usb: {
443
+ supported: false,
444
+ unsupportedReason: 'No USB OTG / USB-Serial lowering on Zephyr (Zephyr USB device stack not wired).',
445
+ partialCoverage: false,
446
+ ops: unsupportedOps('usb.'),
447
+ },
448
+ eth: {
449
+ supported: false,
450
+ unsupportedReason: 'No Ethernet MAC lowering on Zephyr.',
451
+ partialCoverage: false,
452
+ ops: unsupportedOps('eth.'),
453
+ },
454
+ espnow: {
455
+ supported: false,
456
+ unsupportedReason: 'ESP-NOW is an ESP-exclusive wireless protocol; no Zephyr lowering.',
457
+ partialCoverage: false,
458
+ ops: unsupportedOps('espnow.'),
459
+ },
460
+ crypto: {
461
+ supported: false,
462
+ unsupportedReason: 'No hardware crypto (AES/SHA/HMAC) lowering on Zephyr.',
463
+ partialCoverage: false,
464
+ ops: unsupportedOps('crypto.'),
465
+ },
466
+ pcnt: {
467
+ supported: false,
468
+ unsupportedReason: 'No pulse-counter (PCNT) lowering on Zephyr.',
469
+ partialCoverage: false,
470
+ ops: unsupportedOps('pcnt.'),
471
+ },
472
+ mcpwm: {
473
+ supported: false,
474
+ unsupportedReason: 'No motor-control PWM (MCPWM) lowering on Zephyr.',
475
+ partialCoverage: false,
476
+ ops: unsupportedOps('mcpwm.'),
477
+ },
478
+ raw: { supported: true },
479
+ },
480
+
481
+ polyfills: {
482
+ emitted: [
483
+ { id: 'cuttlefish_halt', domain: 'standard', notes: 'Mapped to a k_msleep halt loop (exceptions disabled)' },
484
+ { id: 'timer_methods', domain: 'embedded', notes: 'k_timer + k_work pool (system workqueue); callbacks run in thread context' },
485
+ { id: 'async_runtime', domain: 'embedded', notes: 'Heap-free static Promise/microtask runtime (generateStaticAsyncRuntime), pumped in loop()' },
486
+ ],
487
+ suppressed: [],
488
+ },
489
+
490
+ toolchain: {
491
+ backend: 'west',
492
+ operations: { prepare: true, compile: true, upload: true, monitor: true },
493
+ },
494
+
495
+ libraryResolution: {
496
+ isFrameworkLibraryImport: false,
497
+ getFrameworkLibraryHeaderName: false,
498
+ buildClassNameMap: false,
499
+ tryGenerateLibDecl: false,
500
+ },
501
+
502
+ typeEmission: {
503
+ normalizeCppType: true,
504
+ mathHeader: '<math.h>',
505
+ needsStdString: false,
506
+ needsStdVector: false,
507
+ needsIostream: false,
508
+ needsStdFunction: false,
509
+ stdlibSupport: {
510
+ hasVector: false,
511
+ hasString: false,
512
+ hasIostream: false,
513
+ hasExceptions: false,
514
+ hasRTTI: false,
515
+ recommendedArrayImpl: 'static_array',
516
+ recommendedStringImpl: 'static_string',
517
+ },
518
+ },
519
+
520
+ ambientTypes: ['Preferences'],
521
+
522
+ conformance: {
523
+ // Hardware-test groups — each entry is backed by a tests/<group>.test.ts
524
+ // file in this package (validated by the manifest validator). Mirrors the
525
+ // framework-avr/framework-esp32 convention. These run on metal via
526
+ // `npm run test:hw` (cuttlefish-test transpiles → west build → flash).
527
+ hardwareTestGroups: [
528
+ '01-basics',
529
+ '30-timing',
530
+ '40-gpio',
531
+ '41-analog',
532
+ '42-timers',
533
+ ],
534
+ // Per-op HAL-resolution suite — one tests/packages/framework-zephyr/
535
+ // hal-resolution/<cat>.test.ts per category, snapshotting the exact C++
536
+ // each op lowers to. Mirrors the framework-esp32 convention. These are
537
+ // pure string-snapshot tests (no hardware); they are the safety net that
538
+ // catches regressions like silent pull-resistor / interrupt no-ops.
539
+ halResolutionTests: [
540
+ 'adc', 'ble', 'board', 'dac', 'gpio', 'http', 'i2c', 'interrupts', 'mqtt',
541
+ 'power', 'preferences', 'pulse', 'pwm', 'random', 'spi', 'timing', 'tone',
542
+ 'uart', 'wdt', 'worker',
543
+ ],
544
+ },
545
+ });
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ // ---------------------------------------------------------------------------
2
+ // @typecad/framework-zephyr — public exports
3
+ //
4
+ // The cuttlefish loader (framework-package.ts) requires a `FrameworkStrategy`
5
+ // export (the PlatformStrategy class it instantiates) and an optional
6
+ // `Toolchain`. This package aliases ZephyrStrategy → FrameworkStrategy.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export { ZephyrStrategy as FrameworkStrategy } from './strategy.js';
10
+ export { ZephyrStrategy } from './strategy.js';
11
+ export { Toolchain } from './toolchain/index.js';
12
+
13
+ // Chip descriptor registry (for downstream tooling / additional boards).
14
+ export {
15
+ chipForTarget,
16
+ setActiveChip,
17
+ getActiveChip,
18
+ XIAO_BLE,
19
+ } from './chips/index.js';
20
+ export type {
21
+ ZephyrChipDescriptor,
22
+ ZephyrGpioDtSpec,
23
+ } from './chips/types.js';
@@ -0,0 +1,102 @@
1
+ // ---------------------------------------------------------------------------
2
+ // ADC lowering — nRF SAADC via raw adc_read
3
+ //
4
+ // The XIAO nRF52840 has no pre-declared ADC channel nodes in devicetree, so we
5
+ // cannot use the `adc_dt_spec` convenience. Instead the lowering configures the
6
+ // channel up-front against DEVICE_DT_GET(DT_NODELABEL(adc)) and reads it with
7
+ // `adc_read`. The channel index comes from the chip descriptor's pin→channel
8
+ // map (XIAO D0–D3 = AIN0–AIN3).
9
+ //
10
+ // Channel setup is emitted in adcInitLines() (one per channel used); the ops
11
+ // then just read. A static `adc_channel_cfg` + a per-channel "configured" flag
12
+ // guards the setup, so repeated reads don't reconfigure.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
16
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
17
+
18
+ /** Resolve the SAADC channel index for a HAL pin number; -1 if unmapped. */
19
+ export function adcChannelForPin(chip: ZephyrChipDescriptor, pin: number): number {
20
+ const ch = chip.adc?.channels.find((c) => c.pin === pin);
21
+ return ch ? ch.channel : -1;
22
+ }
23
+
24
+ /**
25
+ * Emit the per-channel ADC setup state. One block per channel in the chip
26
+ * descriptor, each guarded by a `static bool __tc_adc<N>_ready` so the first
27
+ * read configures it and subsequent reads skip. Called from shimLines when
28
+ * the program uses ADC.
29
+ */
30
+ export function adcInitLines(chip: ZephyrChipDescriptor): string[] {
31
+ const dev = `DEVICE_DT_GET(DT_NODELABEL(${chip.adc?.nodeLabel ?? 'adc'}))`;
32
+ const res = chip.adc?.resolution ?? 12;
33
+ const vref = chip.adc?.vrefMv ?? 3000;
34
+ const lines: string[] = ['// CUTTLEFISH_ADC_BEGIN'];
35
+ lines.push(`static const struct device* __tc_adc_dev = ${dev};`);
36
+ for (const c of chip.adc?.channels ?? []) {
37
+ const n = c.channel;
38
+ lines.push(
39
+ `static bool __tc_adc${n}_ready = false;`,
40
+ `static void __tc_adc${n}_setup(void) {`,
41
+ ` if (__tc_adc${n}_ready) return;`,
42
+ ` const struct adc_channel_cfg cfg = {`,
43
+ ` .gain = ADC_GAIN_1_4,`,
44
+ ` .reference = ADC_REF_INTERNAL,`,
45
+ ` .acquisition_time = ADC_ACQ_TIME_DEFAULT,`,
46
+ ` .channel_id = ${n},`,
47
+ ` .differential = 0,`,
48
+ ` };`,
49
+ ` adc_channel_setup(__tc_adc_dev, &cfg);`,
50
+ ` __tc_adc${n}_ready = true;`,
51
+ `}`,
52
+ );
53
+ }
54
+ lines.push(`#define __TC_ADC_VREF_MV ${vref}`);
55
+ lines.push(`#define __TC_ADC_RESOLUTION ${res}`);
56
+ lines.push('// CUTTLEFISH_ADC_END');
57
+ return lines;
58
+ }
59
+
60
+ /**
61
+ * Resolve a HAL adc.* op to Zephyr C++.
62
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
63
+ */
64
+ export function lowerAdc(
65
+ op: HALOpIR,
66
+ chip: ZephyrChipDescriptor,
67
+ ): { code?: string; expression?: string } {
68
+ const o = op as any;
69
+ const res = chip.adc?.resolution ?? 12;
70
+ const vref = chip.adc?.vrefMv ?? 3000;
71
+
72
+ switch (op.operation) {
73
+ case 'adc.read': {
74
+ const ch = adcChannelForPin(chip, o.pin);
75
+ // GCC statement-expression: configures-on-first-call, reads, returns raw.
76
+ return {
77
+ expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); __b; })`,
78
+ };
79
+ }
80
+ case 'adc.read_voltage': {
81
+ const ch = adcChannelForPin(chip, o.pin);
82
+ // Read raw, convert to millivolts via adc_raw_to_millivolts (gain 1/4,
83
+ // internal ref). Returns mV as int.
84
+ return {
85
+ expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); int32_t __v = __b; adc_raw_to_millivolts(${vref}, ADC_GAIN_1_4, ${res}, &__v); __v; })`,
86
+ };
87
+ }
88
+ case 'adc.get_resolution':
89
+ return { expression: String(res) };
90
+ case 'adc.set_reference':
91
+ // Zephyr configures the reference at channel-setup time; runtime switching
92
+ // would require re-setup. Record the intent as a no-op statement.
93
+ return { code: `/* adc.set_reference(${o.reference}): configured at channel setup (ADC_REF_INTERNAL) */` };
94
+ case 'adc.get_reference':
95
+ return { expression: `0 /* DEFAULT (ADC_REF_INTERNAL) */` };
96
+ default:
97
+ throw new Error(
98
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
99
+ `Open an issue or use rawCpp() to emit it manually.`,
100
+ );
101
+ }
102
+ }