@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,308 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Kconfig fragment resolver — extracts the prj.conf symbol logic
3
+ //
4
+ // Previously inline in scaffold.ts as token-scanning of the emitted source.
5
+ // Formalized here as a typed, unit-testable function keyed off a usage-analysis
6
+ // object (the same ctx.analysis.usesX flags the strategy uses). scaffold.ts
7
+ // calls this instead of inlining the scan, keeping prj.conf generation honest
8
+ // and testable.
9
+ // ---------------------------------------------------------------------------
10
+
11
+ export interface KconfigUsage {
12
+ usesAdc?: boolean;
13
+ usesPwm?: boolean;
14
+ usesI2c?: boolean;
15
+ usesSpi?: boolean;
16
+ usesUart?: boolean;
17
+ usesWdt?: boolean;
18
+ usesBle?: boolean;
19
+ usesDisplay?: boolean;
20
+ usesPower?: boolean;
21
+ usesWifi?: boolean;
22
+ usesHttp?: boolean;
23
+ usesMqtt?: boolean;
24
+ usesPreferences?: boolean;
25
+ usesRandom?: boolean;
26
+ /** Touch controller referenced (UI touch adapter emits DT_NODELABEL(ft6336u)). */
27
+ usesTouch?: boolean;
28
+ /** PSRAM type ('opi' | 'quad') when the target board has PSRAM. Emits the
29
+ * CONFIG_SPIRAM symbols so the ESP heap serves PSRAM for canvas allocations. */
30
+ psram?: 'opi' | 'quad';
31
+ }
32
+
33
+ /**
34
+ * Resolve the Kconfig symbol→value map for a prj.conf. Returns a Map preserving
35
+ * insertion order (callers join with '\n'). Core GPIO + C++ + workqueue symbols
36
+ * are always present; driver symbols are usage-gated.
37
+ */
38
+ export function resolveKconfigFragments(
39
+ usage: KconfigUsage,
40
+ debug: boolean,
41
+ ): Map<string, string> {
42
+ const m = new Map<string, string>();
43
+
44
+ // Core driver + console.
45
+ m.set('CONFIG_GPIO', 'y');
46
+ m.set('CONFIG_PRINTK', 'y');
47
+ m.set('CONFIG_PRINTK_SYNC', 'y');
48
+ m.set('CONFIG_CONSOLE', 'y');
49
+
50
+ if (usage.usesAdc) m.set('CONFIG_ADC', 'y');
51
+ if (usage.usesPwm) m.set('CONFIG_PWM', 'y');
52
+ if (usage.usesI2c) m.set('CONFIG_I2C', 'y');
53
+ if (usage.usesSpi) m.set('CONFIG_SPI', 'y');
54
+ if (usage.usesWdt) m.set('CONFIG_WATCHDOG', 'y');
55
+ if (usage.usesDisplay) {
56
+ m.set('CONFIG_DISPLAY', 'y');
57
+ m.set('CONFIG_SPI', 'y');
58
+ m.set('CONFIG_MIPI_DBI', 'y');
59
+ // Enable GDMA so the ESP32 SPI driver uses DMA for panel transfers instead
60
+ // of PIO through the 64-byte hardware FIFO. Without DMA a full 480x320 fill
61
+ // takes ~110ms (effectively ~4MHz); with DMA the same transfer runs at the
62
+ // configured SPI clock (~80MHz) and drops into the low tens of ms. The
63
+ // display overlay pairs this with dma-enabled + dmas on the spi2 node.
64
+ m.set('CONFIG_DMA', 'y');
65
+ // Disable the MIPI DBI SPI bridge + ST7796S drivers. The display adapter
66
+ // drives the panel directly via spi_write. Binding these drivers would
67
+ // allocate a tearing-effect GPIO interrupt that conflicts with the SPI/I2C
68
+ // driver interrupts — the VECDESC_FL_SHARED assertion crashes on touch.
69
+ m.set('CONFIG_MIPI_DBI_SPI', 'n');
70
+ m.set('CONFIG_ST7796S', 'n');
71
+ }
72
+ if (usage.usesTouch) {
73
+ m.set('CONFIG_I2C', 'y'); // FT6336U touch on I2C
74
+ }
75
+ // PSRAM: enable the ESP SPIRAM driver + route malloc/heap to external RAM so
76
+ // large canvas allocations (scroll viewports, lists) can use PSRAM instead of
77
+ // failing in internal SRAM. Zephyr's ESP32 PSRAM support uses CONFIG_ESP_SPIRAM
78
+ // (not CONFIG_SPIRAM — that's an ESP-IDF symbol). The mode choice selects the
79
+ // PSRAM type: OCT for OPI (ESP32-S3), QUAD for quad-spi. CONFIG_ESP_SPIRAM
80
+ // selects SHARED_MULTI_HEAP automatically, which routes heap_caps_malloc to
81
+ // PSRAM. The SoC dtsi already carries the psram0 DT node.
82
+ if (usage.psram) {
83
+ m.set('CONFIG_ESP_SPIRAM', 'y');
84
+ if (usage.psram === 'opi') {
85
+ m.set('CONFIG_SPIRAM_MODE_OCT', 'y');
86
+ } else {
87
+ m.set('CONFIG_SPIRAM_MODE_QUAD', 'y');
88
+ }
89
+ }
90
+ // deep_sleep_pin wake needs PM + PM_DEVICE.
91
+ if (usage.usesPower) {
92
+ m.set('CONFIG_PM', 'y');
93
+ m.set('CONFIG_PM_DEVICE', 'y');
94
+ }
95
+ if (usage.usesWifi) {
96
+ // Master networking switch — every CONFIG_NET_* symbol depends on NETWORKING
97
+ // (without it, Kconfig silently forces them all to n).
98
+ m.set('CONFIG_NETWORKING', 'y');
99
+ m.set('CONFIG_WIFI', 'y');
100
+ m.set('CONFIG_WIFI_ESP32', 'y'); // ESP32-specific driver (sole WiFi target)
101
+ m.set('CONFIG_NET_L2_ETHERNET', 'y');
102
+ m.set('CONFIG_NET_IPV4', 'y');
103
+ m.set('CONFIG_NET_UDP', 'y'); // transitive dep of NET_DHCPV4
104
+ m.set('CONFIG_NET_DHCPV4', 'y');
105
+ // NOT CONFIG_NET_CONFIG_SETTINGS: that runs net_config_init() at boot which
106
+ // BLOCKS up to NET_CONFIG_INIT_TIMEOUT (default 30s) waiting for the iface
107
+ // to come up — but our shim brings the iface up itself in setup() (connect),
108
+ // so net_config waits the full 30s, then the dual management of the same
109
+ // iface crashes the driver. Our shim owns connectivity (net_mgmt connect/
110
+ // disconnect + conn_mgr monitor for L4), exactly like the standalone Zephyr
111
+ // WiFi samples that omit NET_CONFIG_SETTINGS.
112
+ m.set('CONFIG_NET_MGMT', 'y');
113
+ m.set('CONFIG_NET_MGMT_EVENT', 'y'); // required for the net_mgmt callbacks
114
+ m.set('CONFIG_NET_CONNECTION_MANAGER', 'y'); // conn_mgr — the connect portability layer
115
+ // Networking stack sizes. The defaults are tiny (NET_MGMT_EVENT_STACK_SIZE
116
+ // is 768 on non-x86) and the WiFi connect result/event handlers run on that
117
+ // stack — overflowing it freezes the chip mid-connect (silent hard fault,
118
+ // no panic dump). The official Zephyr WiFi samples (samples/net/wifi/*)
119
+ // bump exactly these; mirror them. MAIN_STACK 4096→5200 because esp_wifi
120
+ // device init is stack-hungry and 4096 is marginal on the ESP32-S3.
121
+ m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
122
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
123
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
124
+ m.set('CONFIG_MAIN_STACK_SIZE', '5200');
125
+ // NOTE: wifi.set_tx_power needs no Kconfig symbol. esp_wifi_set_max_tx_power
126
+ // programs the radio at runtime; its ceiling is baked into the prebuilt
127
+ // libphy.a / PHY init data, not a prj.conf knob. The ESP-IDF symbol
128
+ // ESP_PHY_MAX_WIFI_TX_POWER lives in components/esp_phy/Kconfig, which the
129
+ // Zephyr module integration does NOT source — assigning it here would abort
130
+ // the build ("undefined symbol").
131
+ }
132
+ if (usage.usesHttp) {
133
+ // HTTP/S rides on the networking stack. The shim does its own
134
+ // socket/getaddrinfo/connect, so it needs the BSD socket layer + POSIX
135
+ // DNS surface + the http client lib, plus the TLS sockopt layer (which
136
+ // selects mbedTLS) and the DNS resolver. HTTP needs the same IP base as
137
+ // WiFi, so this emits the networking primitives even when usesWifi is
138
+ // false — an http-only program still has to reach the internet. Map.set
139
+ // is idempotent, so overlaps with the wifi block are harmless.
140
+ m.set('CONFIG_NETWORKING', 'y');
141
+ m.set('CONFIG_NET_IPV4', 'y');
142
+ m.set('CONFIG_NET_DHCPV4', 'y');
143
+ m.set('CONFIG_NET_TCP', 'y'); // http_client_req needs a TCP socket
144
+ // NET_MAX_CONTEXTS caps the network 5-tuple (socket) pool — the default 6 is
145
+ // exhausted after a handful of sequential HTTP requests even when each is
146
+ // closed (closed TCP contexts linger in TIME_WAIT), and socket() then returns
147
+ // -EPERM. The hardware CRUD harness makes ~17 sequential requests, so raise
148
+ // this well above the default.
149
+ m.set('CONFIG_NET_MAX_CONTEXTS', '16');
150
+ // NET_MAX_CONN is the connection-registry pool (default 8 with v4+v6), a
151
+ // *separate* limit from NET_MAX_CONTEXTS. Closed TCP entries linger briefly
152
+ // in the registry, so rapid sequential HTTP requests exhaust the default and
153
+ // connect() then returns -EPERM. Pair it with NET_MAX_CONTEXTS so both the
154
+ // socket (5-tuple) and connection-registry pools have headroom.
155
+ m.set('CONFIG_NET_MAX_CONN', '16');
156
+ // ZVFS_OPEN_MAX must be set above 0 or socket() returns -EPERM (errno 1): the
157
+ // fd table is allocated to exactly ZVFS_OPEN_MAX entries, and the default 0
158
+ // (even with the ZVFS_OPEN_ADD_SIZE_* "min" contributors) yields zero usable
159
+ // descriptors. HTTP tests open/close a socket per request; give headroom over
160
+ // the per-subsystem contributors (NET=6, POSIX=3).
161
+ m.set('CONFIG_ZVFS_OPEN_MAX', '16');
162
+ // NET_SOCKETS is the user-facing switch for the BSD socket API + ZVFS. The
163
+ // shim uses bare POSIX names (connect/socket/close/getaddrinfo/freeaddrinfo)
164
+ // rather than the zsock_ forms; those bare names resolve under CONFIG_POSIX_API
165
+ // (the official samples/net/sockets/http_client sample sets exactly this).
166
+ // Without it, <zephyr/posix/unistd.h> gates `int close(int)` behind
167
+ // #ifdef CONFIG_POSIX_API and the build fails with "'close' was not declared".
168
+ m.set('CONFIG_NET_SOCKETS', 'y');
169
+ m.set('CONFIG_POSIX_API', 'y');
170
+ m.set('CONFIG_DNS_RESOLVER', 'y'); // getaddrinfo for hostnames
171
+ m.set('CONFIG_DNS_SERVER_IP_ADDRESSES', 'y');
172
+ m.set('CONFIG_HTTP_CLIENT', 'y'); // <zephyr/net/http/client.h> + http_client_req
173
+ // HTTPS via Zephyr socket TLS (IPPROTO_TLS_1_2 + SOL_TLS sockopts, driven by
174
+ // the shim's __tc_http_open_socket https branch). NET_SOCKETS_SOCKOPT_TLS
175
+ // selects mbedTLS, but its ssl layer (mbedtls_ssl_*) needs the rest of this
176
+ // matrix to actually link + a working TLS 1.2 protocol + key exchange.
177
+ m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
178
+ m.set('CONFIG_TLS_CREDENTIALS', 'y'); // tls_credential_add for caCert()
179
+ m.set('CONFIG_MBEDTLS', 'y');
180
+ m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
181
+ // Enable TLS 1.2 via a single ciphersuite rather than the broad
182
+ // SSL_PROTO_TLS1_2 + KEY_EXCHANGE_ALL_ENABLED (the latter selects KEXes whose
183
+ // PSA_WANT_* deps are unsatisfied → Kconfig abort) or a bare
184
+ // SSL_PROTO_TLS1_2 (no key exchange → check_config.h "no key exchange
185
+ // methods defined"). A ciphersuite is the proven path the in-tree HTTPS
186
+ // samples use (samples/net/prometheus): it transitively selects
187
+ // MBEDTLS_SSL_PROTO_TLS1_2 + its one key exchange + every PSA_WANT_* key/alg
188
+ // that key exchange needs, with no dangling deps. ECDHE_RSA matches the test
189
+ // server's RSA cert (rsa:2048) and is widely offered; add more ciphersuites
190
+ // here to broaden server compatibility.
191
+ m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
192
+ // Handshake/protocol buffers allocate from the mbedTLS heap; MBEDTLS_HEAP_SIZE
193
+ // must hold ~2x MBEDTLS_SSL_MAX_CONTENT_LEN plus working state. 65000 fits on
194
+ // the ESP32; mbedTLS requires the full libc and PEM (not DER) cert format.
195
+ m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
196
+ m.set('CONFIG_MBEDTLS_HEAP_SIZE', '65000');
197
+ m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
198
+ m.set('CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT', 'y');
199
+ m.set('CONFIG_PSA_CRYPTO', 'y');
200
+ m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
201
+ // The http client + TLS handshake are stack-hungry; mirror the wifi bumps
202
+ // (NET_*_STACK_SIZE) and keep the larger main-stack value (TLS handshake
203
+ // overflows the default main stack).
204
+ m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
205
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
206
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
207
+ if (!usage.usesWifi) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
208
+ }
209
+ if (usage.usesMqtt) {
210
+ // MQTT rides on the networking stack. CONFIG_MQTT_LIB selects NET_SOCKETS;
211
+ // the shim getaddrinfo-resolves the broker and runs its own poll k_thread.
212
+ // CONFIG_MQTT_LIB_TLS enables MQTT_TRANSPORT_SECURE + mqtt_sec_config; the
213
+ // mbedTLS matrix is the same one HTTP uses (it's idempotent via Map.set).
214
+ m.set('CONFIG_NETWORKING', 'y');
215
+ m.set('CONFIG_NET_IPV4', 'y');
216
+ m.set('CONFIG_NET_DHCPV4', 'y');
217
+ m.set('CONFIG_NET_TCP', 'y');
218
+ m.set('CONFIG_NET_SOCKETS', 'y');
219
+ m.set('CONFIG_MQTT_LIB', 'y');
220
+ m.set('CONFIG_MQTT_LIB_TLS', 'y');
221
+ // NET_MAX_CONTEXTS / NET_MAX_CONN: same exhaustion risk as HTTP — sequential
222
+ // connections linger after close. Give the broker session + headroom.
223
+ m.set('CONFIG_NET_MAX_CONTEXTS', '16');
224
+ m.set('CONFIG_NET_MAX_CONN', '16');
225
+ m.set('CONFIG_ZVFS_OPEN_MAX', '16');
226
+ // mbedTLS matrix for mqtts:// (mirrors the HTTP block; overlaps are harmless).
227
+ m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
228
+ m.set('CONFIG_TLS_CREDENTIALS', 'y');
229
+ m.set('CONFIG_MBEDTLS', 'y');
230
+ m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
231
+ m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
232
+ m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
233
+ m.set('CONFIG_MBEDTLS_HEAP_SIZE', '65000');
234
+ m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
235
+ m.set('CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT', 'y');
236
+ m.set('CONFIG_PSA_CRYPTO', 'y');
237
+ m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
238
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
239
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
240
+ if (!usage.usesWifi && !usage.usesHttp) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
241
+ }
242
+ if (usage.usesBle) {
243
+ m.set('CONFIG_BT', 'y');
244
+ m.set('CONFIG_BT_PERIPHERAL', 'y');
245
+ m.set('CONFIG_BT_GATT_DYNAMIC_DB', 'y');
246
+ }
247
+ // Preferences: ZMS-backed settings. CONFIG_SETTINGS_ZMS depends on ZMS +
248
+ // FLASH_MAP (it does NOT select them), so all three must be set explicitly.
249
+ // The backend locates the storage_partition fixed-partition automatically
250
+ // (or the /chosen zephyr,settings-partition — see dt-config/overlay.ts); no
251
+ // partition macro is needed in the shim. ZMS is preferred over NVS per the
252
+ // Zephyr docs ("as of 4.1 the recommended backend is NVS or ZMS").
253
+ if (usage.usesPreferences) {
254
+ m.set('CONFIG_FLASH', 'y');
255
+ m.set('CONFIG_FLASH_MAP', 'y');
256
+ m.set('CONFIG_ZMS', 'y');
257
+ m.set('CONFIG_SETTINGS', 'y');
258
+ m.set('CONFIG_SETTINGS_ZMS', 'y');
259
+ }
260
+ // usesUart: the board enables the console UART by default; the overlay (not
261
+ // Kconfig) is where a UART node would be enabled, so no symbol here.
262
+ // Random: <zephyr/random/random.h> sys_rand_get is backed by the random
263
+ // generator subsystem. CONFIG_RANDOM_GENERATOR is the umbrella that selects a
264
+ // working backend for the board (nRF52840 → hardware RNG via the entropy
265
+ // driver; QEMU/host → the test generator). CONFIG_ENTROPY_GENERATOR is its
266
+ // hard dependency on hardware targets. Both default on for most boards, but
267
+ // setting them explicitly keeps the symbol set honest and survives a board
268
+ // whose defconfig leaves them off.
269
+ if (usage.usesRandom) {
270
+ m.set('CONFIG_ENTROPY_GENERATOR', 'y');
271
+ m.set('CONFIG_RANDOM_GENERATOR', 'y');
272
+ }
273
+
274
+ // System workqueue — bumped for worker-offload AND timer callbacks. The
275
+ // workqueue itself is unconditionally built (no CONFIG_SYSTEM_WORKQUEUE symbol
276
+ // exists in Zephyr — that was a phantom that broke real builds); only the
277
+ // stack size is a real Kconfig knob.
278
+ m.set('CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE', '8192');
279
+
280
+ // C++ support.
281
+ m.set('CONFIG_CPP', 'y');
282
+ m.set('CONFIG_NEWLIB_LIBC', 'y');
283
+ m.set('CONFIG_REQUIRES_FULL_LIBCPP', 'y');
284
+ m.set('CONFIG_STD_CPP14', 'y');
285
+
286
+ // Main thread stack. WiFi already bumps this to 5200 (esp_wifi device init
287
+ // is stack-hungry); HTTP/MQTT + TLS also bump it (the mbedTLS handshake is
288
+ // stack-hungry). The UI runtime (ui_tick) renders a large node tree with AA
289
+ // text + canvas compositing per frame, so 4096 is marginal headroom and we
290
+ // bump to 8192 for displays. NOTE: a Zephyr panic dump that prints
291
+ // `EXCCAUSE 63` is NOT necessarily a stack overflow — on the Zephyr Xtensa
292
+ // port EXCCAUSE 63 is the reserved software-exception used for k_oops/abort,
293
+ // and the ESP32 port reaches it via abort() in intc_esp32.c (the
294
+ // esp_intr_noniram_disable/enable unbalanced-flag guards). Resolve the dump's
295
+ // PC against the .elf (xtensa_arch_except → abort) before treating it as a
296
+ // stack overflow; 8192 is kept here because deep ui_tick call nesting still
297
+ // wants the headroom.
298
+ if (!usage.usesWifi && !usage.usesHttp && !usage.usesMqtt) {
299
+ m.set('CONFIG_MAIN_STACK_SIZE', usage.usesDisplay ? '8192' : '4096');
300
+ }
301
+
302
+ if (debug) {
303
+ m.set('CONFIG_DEBUG', 'y');
304
+ m.set('CONFIG_DEBUG_OPTIMIZATIONS', 'y');
305
+ }
306
+
307
+ return m;
308
+ }
@@ -0,0 +1,293 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Devicetree overlay generator
3
+ //
4
+ // Generates <project>/app/boards/<board>.overlay — the standard Zephyr overlay
5
+ // path. Content is additive: for each used peripheral with a DT binding the chip
6
+ // descriptor knows, emit a `&nodelabel { status = "okay"; }` block enabling it.
7
+ // West merges this over the board's base DT; we never rewrite the base.
8
+ //
9
+ // This is the Zephyr analog of Arduino's library-resolution hooks: the artifact
10
+ // that brings external capabilities into a build. (Arduino does it by parsing
11
+ // library headers into .d.ts; Zephyr does it by enabling DT nodes + Kconfig.)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
15
+ import type { ZephyrDisplayProfile } from '../display/profiles.js';
16
+ import type { KconfigUsage } from './kconfig.js';
17
+
18
+ /**
19
+ * Generate the overlay source for a board + usage. Returns the overlay text
20
+ * (caller writes it to app/boards/<board>.overlay via writeIfChanged).
21
+ */
22
+ /** Display wiring from cuttlefish.config.ts (cs/dc/rst/spiFrequency/spiPins). */
23
+ export interface DisplayWiring {
24
+ cs?: number;
25
+ dc?: number;
26
+ rst?: number;
27
+ spiFrequency?: number;
28
+ /** SPI bus pins. When present, the overlay remuxes the SPI controller's
29
+ * pinctrl to these pins (the board defaults rarely match a breakout's
30
+ * wiring — e.g. demo-st's panel is on SCK=18/MOSI=23, not the devkitc
31
+ * default 12/11). */
32
+ sck?: number;
33
+ mosi?: number;
34
+ miso?: number;
35
+ /** Optional GPIO driving the panel backlight. When set, the overlay emits a
36
+ * `gpio-leds` node + DT alias (named by the profile's `backlight`) so the
37
+ * display adapter can drive it high at init. When absent (e.g. the panel's
38
+ * backlight is hardwired to 3.3V), no node or alias is emitted — the C++
39
+ * backlight init is guarded by `DT_HAS_ALIAS` and compiles away. */
40
+ backlightPin?: number;
41
+ }
42
+
43
+ /** Touch wiring from cuttlefish.config.ts (irq/resetPin/sda/scl). */
44
+ export interface TouchWiring {
45
+ irq?: number;
46
+ resetPin?: number;
47
+ sda?: number;
48
+ scl?: number;
49
+ }
50
+
51
+ export function generateOverlay(
52
+ chip: ZephyrChipDescriptor,
53
+ usage: KconfigUsage,
54
+ display: ZephyrDisplayProfile | undefined,
55
+ wiring?: DisplayWiring,
56
+ touch?: TouchWiring,
57
+ ): string {
58
+ const lines: string[] = [
59
+ '/* Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts. */',
60
+ '/* Enables peripherals the program uses. West merges this over the board DT. */',
61
+ '',
62
+ ];
63
+
64
+ // Pinctrl override blocks reference the esp32s3 pinmux tokens; the include
65
+ // must precede them. Emitted when the display SPI pins are overridden.
66
+ if (wiring?.sck !== undefined && wiring?.mosi !== undefined) {
67
+ lines.splice(2, 0, '#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>', '');
68
+ }
69
+
70
+ const block = (label: string, extra: string[] = []): void => {
71
+ lines.push(`&${label} {`);
72
+ lines.push(` status = "okay";`);
73
+ for (const e of extra) lines.push(` ${e}`);
74
+ lines.push('};');
75
+ lines.push('');
76
+ };
77
+
78
+ if (usage.usesI2c && chip.i2c) {
79
+ for (const c of chip.i2c.controllers) block(c.nodeLabel);
80
+ }
81
+ if (usage.usesSpi && chip.spi) {
82
+ for (const c of chip.spi.controllers) block(c.nodeLabel);
83
+ }
84
+ if (usage.usesUart && chip.uart) {
85
+ for (const c of chip.uart.controllers) block(c.nodeLabel);
86
+ }
87
+ if (display) {
88
+ // Emit a full display DT node definition. Boards like the ESP32 devkit
89
+ // have no display node in their base DT, so a bare `&display0 { status }`
90
+ // fails (the nodelabel doesn't exist). Define the node attached to a SPI
91
+ // controller with the panel's compatible string + dimensions. The pin
92
+ // wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
93
+ // real board overlay would carry its own binding.
94
+ emitDisplayNode(lines, display, wiring);
95
+ }
96
+ // FT6336U touch on I2C — defined when the program uses touch (the UI touch
97
+ // adapter references DT_NODELABEL(ft6336u)). Same rationale: the bare devkit
98
+ // has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it emits
99
+ // even when the chip descriptor doesn't declare I2C controllers (ESP32).
100
+ if (usage.usesTouch) {
101
+ emitTouchNode(lines, touch);
102
+ }
103
+
104
+ // Preferences (ZMS settings backend): point the settings subsystem at the
105
+ // board's storage_partition. The backend looks for /chosen
106
+ // zephyr,settings-partition first, then falls back to a fixed-partition
107
+ // labeled storage_partition — most Zephyr boards define that label, so this
108
+ // chosen entry makes the lookup explicit and survives boards that name the
109
+ // partition differently. It only adds a /chosen pointer (never redeclares
110
+ // the partition node — west errors if a node is multiply-defined).
111
+ if (usage.usesPreferences) {
112
+ lines.push('/ {');
113
+ lines.push(' chosen {');
114
+ lines.push(' zephyr,settings-partition = &storage_partition;');
115
+ lines.push(' };');
116
+ lines.push('};');
117
+ lines.push('');
118
+ }
119
+
120
+ // PSRAM: enable the psram0 DT node with the correct size. The devkitc board
121
+ // DT defaults to a no-PSRAM module variant (e.g. wroom_n8); a PSRAM-capable
122
+ // module (N16R8, N8R8) needs the node enabled + sized so the linker maps
123
+ // .ext_ram sections into the real PSRAM. OPI on ESP32-S3 = 8MB octal PSRAM.
124
+ if (usage.psram) {
125
+ lines.push('&psram0 {');
126
+ lines.push(' status = "okay";');
127
+ lines.push(' size = <(DT_SIZE_M(8))>;');
128
+ lines.push('};');
129
+ lines.push('');
130
+ }
131
+
132
+ return lines.join('\n');
133
+ }
134
+
135
+ /**
136
+ * Emit a display DT node definition. The node is attached to spi2 (the ESP32's
137
+ * first user SPI controller) via a MIPI DBI SPI bridge. Pin wiring comes from
138
+ * the display config (cs/dc/rst); defaults match the demo-st wiring if absent.
139
+ */
140
+ function emitDisplayNode(lines: string[], display: ZephyrDisplayProfile, wiring?: DisplayWiring): void {
141
+ const dc = wiring?.dc ?? 17;
142
+ const rst = wiring?.rst ?? 16;
143
+ const cs = wiring?.cs ?? 5;
144
+ const freq = wiring?.spiFrequency ?? 80000000;
145
+ // DT node describes the NATIVE panel geometry; the effective (rotated)
146
+ // dimensions live in the display profile.
147
+ const nativeW = display.nativeWidth ?? display.width;
148
+ const nativeH = display.nativeHeight ?? display.height;
149
+ const sck = wiring?.sck;
150
+ const mosi = wiring?.mosi;
151
+ const miso = wiring?.miso;
152
+ // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
153
+ const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
154
+ // The board's spim2_default pinctrl group usually targets the devkitc's
155
+ // default SPI pins (SCLK=12/MOSI=11), which rarely match the display's
156
+ // wiring. When the config declares spiPins, override the pinctrl groups to
157
+ // remux the controller onto the panel's actual pins. Raw ESP32_PINMUX()
158
+ // macros are used instead of the named SPIM2_*_GPIOxx tokens because the
159
+ // bindings header omits GPIOs 22-25 from those lists.
160
+ if (sck !== undefined && mosi !== undefined) {
161
+ lines.push('&spim2_default {');
162
+ lines.push(' group1 {');
163
+ lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
164
+ lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
165
+ lines.push(' <ESP32_PINMUX(10, ESP_NOSIG, ESP_FSPICS0_OUT)>;');
166
+ lines.push(' };');
167
+ lines.push(' group2 {');
168
+ lines.push(` pinmux = <ESP32_PINMUX(${mosi}, ESP_NOSIG, ESP_FSPID_OUT)>;`);
169
+ lines.push(' output-low;');
170
+ lines.push(' };');
171
+ lines.push('};');
172
+ lines.push('');
173
+ }
174
+ lines.push('&dma {');
175
+ lines.push(' status = "okay";');
176
+ lines.push('};');
177
+ lines.push('');
178
+ lines.push('&spi2 {');
179
+ lines.push(' status = "okay";');
180
+ lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
181
+ // Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
182
+ // dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
183
+ // without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
184
+ // 80MHz requested). The GDMA driver allocates channels per peripheral based
185
+ // on spi2's dma-host=<0> (set in the SoC dtsi), so the channel cells are the
186
+ // GDMA instance's rx/tx slot ids (2 here = an SPI2-dedicated pair; the GDMA
187
+ // binding's #dma-cells = <1> carries the channel).
188
+ lines.push(' dma-enabled;');
189
+ lines.push(' dmas = <&dma 0>, <&dma 1>;');
190
+ lines.push(' dma-names = "rx", "tx";');
191
+ lines.push('};');
192
+ lines.push('');
193
+ lines.push('/ {');
194
+ lines.push(' mipi_dbi: mipi-dbi {');
195
+ lines.push(' compatible = "zephyr,mipi-dbi-spi";');
196
+ lines.push(' spi-dev = <&spi2>;');
197
+ lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
198
+ lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
199
+ lines.push(' write-only;');
200
+ lines.push(' #address-cells = <1>;');
201
+ lines.push(' #size-cells = <0>;');
202
+ lines.push(` ${display.dtLabel}: display@0 {`);
203
+ lines.push(' compatible = "sitronix,st7796s";');
204
+ lines.push(' reg = <0>;');
205
+ lines.push(` mipi-max-frequency = <${freq}>;`);
206
+ lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
207
+ lines.push(` width = <${nativeW}>;`);
208
+ lines.push(` height = <${nativeH}>;`);
209
+ // MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
210
+ // direct-drive init (0x28). The DT copy keeps the stock driver's init
211
+ // consistent if it is ever exercised.
212
+ lines.push(' madctl = <0x28>;');
213
+ lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
214
+ lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
215
+ lines.push(' };');
216
+ lines.push(' };');
217
+ lines.push('};');
218
+ lines.push('');
219
+ // Backlight node: emit only when the config provides a backlight GPIO. The
220
+ // pin was previously hardcoded to 4, which collided with the FT6336U
221
+ // reset-gpios on the same pin (demo-st's backlight is hardwired to 3.3V, so
222
+ // no backlight node is correct there). When present, use the configured pin
223
+ // and the profile's alias name so the display adapter can drive it.
224
+ const blPin = wiring?.backlightPin;
225
+ if (display.backlight && blPin !== undefined) {
226
+ lines.push('/ {');
227
+ lines.push(' aliases {');
228
+ lines.push(` ${display.backlight} = &bl_led;`);
229
+ lines.push(' };');
230
+ lines.push(' bl_gpio: bl-gpio-leds {');
231
+ lines.push(' compatible = "gpio-leds";');
232
+ lines.push(' bl_led: led {');
233
+ lines.push(` gpios = <&${gpioController(blPin)} ${blPin} GPIO_ACTIVE_HIGH>;`);
234
+ lines.push(' };');
235
+ lines.push(' };');
236
+ lines.push('};');
237
+ lines.push('');
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Emit an FT6336U touch DT node on the first I2C controller. The node address
243
+ * is the FT6336U default (0x38). The UI touch adapter references
244
+ * DT_NODELABEL(ft6336u). Pin wiring (irq/resetPin/sda/scl) comes from the
245
+ * touch config; defaults match the demo-st wiring if absent.
246
+ */
247
+ function emitTouchNode(lines: string[], touch?: TouchWiring): void {
248
+ const irq = touch?.irq ?? 15;
249
+ const resetPin = touch?.resetPin;
250
+ const sda = touch?.sda;
251
+ const scl = touch?.scl;
252
+ // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
253
+ const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
254
+ // Zephyr has no ft6336 binding — the FT6336U is register-compatible with the
255
+ // ft5336 binding (same Focaltech register map: TD_STATUS at 0x02, coordinate
256
+ // registers 0x03-0x06). The C++ touch adapter reads these directly via I2C,
257
+ // so the binding just needs to exist for the DT node to resolve.
258
+ // When SDA/SCL are provided, remux the I2C bus to those pins via pinctrl
259
+ // (the board's default I2C pins rarely match a breakout's wiring).
260
+ if (sda !== undefined && scl !== undefined) {
261
+ lines.push('&pinctrl {');
262
+ lines.push(' i2c0_touch: i2c0_touch {');
263
+ lines.push(' group1 {');
264
+ lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
265
+ lines.push(' bias-pull-up;');
266
+ lines.push(' drive-open-drain;');
267
+ lines.push(' };');
268
+ lines.push(' };');
269
+ lines.push('};');
270
+ lines.push('');
271
+ lines.push('&i2c0 {');
272
+ lines.push(' status = "okay";');
273
+ lines.push(' pinctrl-0 = <&i2c0_touch>;');
274
+ lines.push(' pinctrl-names = "default";');
275
+ } else {
276
+ lines.push('&i2c0 {');
277
+ lines.push(' status = "okay";');
278
+ }
279
+ lines.push(' ft6336u: ft6336u@38 {');
280
+ lines.push(' compatible = "focaltech,ft5336";');
281
+ lines.push(' reg = <0x38>;');
282
+ // NOTE: int-gpios is intentionally omitted. The ft5336 Zephyr driver
283
+ // registers a GPIO interrupt on int-gpios, which triggers an assertion
284
+ // failure in the ESP32 interrupt controller (VECDESC_FL_SHARED conflict).
285
+ // The cuttlefish touch adapter polls touch_isTouched() via I2C every frame
286
+ // — it never uses the IRQ pin, so the interrupt registration is unnecessary.
287
+ if (resetPin !== undefined) {
288
+ lines.push(` reset-gpios = <&${gpioController(resetPin)} ${resetPin} GPIO_ACTIVE_LOW>;`);
289
+ }
290
+ lines.push(' };');
291
+ lines.push('};');
292
+ lines.push('');
293
+ }