@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
@@ -1,505 +1,647 @@
1
- import { SENSOR_PART_INFO } from '@typecad/hal';
2
-
3
- /** A sensor part's catalog facts, as the exceptions helper consumes them. */
4
- interface SensorKconfigSource {
5
- kconfig: readonly string[];
6
- }
7
-
8
- /**
9
- * Apply the per-part Kconfig exceptions the generated catalog records, for
10
- * exactly the parts the program constructs. Lines are CONFIG_<SYM>=<value>
11
- * strings parsed into the map; a symbol the user set explicitly in
12
- * zephyr.kconfig is left alone (scaffold.ts already skips user overrides for
13
- * auto symbols, and Map.set idempotence covers the rest). The lookup is a
14
- * parameter so tests can drive it with a synthetic catalog.
15
- */
16
- export function applySensorKconfigExceptions(
17
- m: Map<string, string>,
18
- parts: readonly { part: string }[] | undefined,
19
- lookup: Readonly<Record<string, SensorKconfigSource>> = SENSOR_PART_INFO,
20
- ): void {
21
- if (!parts) return;
22
- for (const sp of parts) {
23
- for (const line of lookup[sp.part]?.kconfig ?? []) {
24
- const eq = line.indexOf('=');
25
- if (eq <= 0) continue;
26
- m.set(line.slice(0, eq), line.slice(eq + 1));
27
- }
28
- }
29
- }
30
-
31
- // ---------------------------------------------------------------------------
32
- // Kconfig fragment resolver — extracts the prj.conf symbol logic
33
- //
34
- // Previously inline in scaffold.ts as token-scanning of the emitted source.
35
- // Formalized here as a typed, unit-testable function keyed off a usage-analysis
36
- // object (the same ctx.analysis.usesX flags the strategy uses). scaffold.ts
37
- // calls this instead of inlining the scan, keeping prj.conf generation honest
38
- // and testable.
39
- // ---------------------------------------------------------------------------
40
-
41
- export interface KconfigUsage {
42
- usesAdc?: boolean;
43
- usesPwm?: boolean;
44
- usesDac?: boolean;
45
- usesFS?: boolean;
46
- usesHwtimer?: boolean;
47
- usesI2c?: boolean;
48
- usesSpi?: boolean;
49
- usesUart?: boolean;
50
- /** USB CDC-ACM serial used (usb.* ops). Selects the "next" USB device
51
- * stack + CDC class; the class instances themselves are composed in the
52
- * DT overlay and auto-default on once the node exists (assigning the
53
- * class symbol keeps prj.conf explicit and survives DT-only probes). */
54
- usesUsb?: boolean;
55
- usesWdt?: boolean;
56
- usesBle?: boolean;
57
- usesDisplay?: boolean;
58
- usesWifi?: boolean;
59
- usesHttp?: boolean;
60
- usesMqtt?: boolean;
61
- usesPreferences?: boolean;
62
- usesRandom?: boolean;
63
- /** A printf-family format specifier with a float conversion (%f, %.2f, %e,
64
- * …) appears in the emitted source — Zephyr's cbprintf only links float
65
- * conversions with FP_SUPPORT (which itself needs the COMPLETE impl). */
66
- usesFloatFormat?: boolean;
67
- /** DT-bound sensor parts used (sensor.* ops — the generic catalog). The
68
- * umbrella under which every driver sensor Kconfig lives (`if SENSOR`);
69
- * the per-driver symbols default on from their DT node presence. */
70
- usesSensor?: boolean;
71
- /** Distinct constructed sensors — only the overlay generator consumes this
72
- * (one DT child node per entry, on the given I2C controller index); prj.conf
73
- * ignores it. Same adcReadPins/pwmUsedPins pattern. */
74
- sensorParts?: readonly { part: string; busIndex: number; port: number; busKind: 'i2c' | 'spi'; spiHz?: number; spiMode?: number; alertPin?: number }[];
75
- /** Distinct thin SPI targets (hal/spi-target.ts) — one DT child node per
76
- * entry (no compatible — a raw spi_dt_spec peer), appended after sensor
77
- * CS entries in the controller's merged cs-gpios. */
78
- spiTargets?: readonly { busIndex: number; cs: number; hz?: number; mode?: number }[];
79
- /** Touch controller referenced (UI touch adapter emits DT_NODELABEL(ft6336u)
80
- * or DT_NODELABEL(xpt2046)). Selects the bus driver the node needs. */
81
- usesTouch?: boolean;
82
- /** Which touch controller the program uses — FT6336U rides I2C, XPT2046
83
- * rides the display's SPI bus. Only meaningful with usesTouch. */
84
- touchController?: 'ft6336u' | 'xpt2046';
85
- /** The emitted shim carries the STM32F4 DBGMCU keep-SWD-alive init
86
- * (__tc_stm32_dbgmcu token). Selects Zephyr's own "debugger attach in
87
- * stop/sleep" init (sets DBG_STOP via the LL headers); the shim's raw
88
- * register poke additionally covers DBG_SLEEP, which the Zephyr F4 path
89
- * does not set. */
90
- usesStm32DebugSleep?: boolean;
91
- /** PSRAM type ('opi' | 'quad') when the target board has PSRAM. Emits the
92
- * CONFIG_SPIRAM symbols so the ESP heap serves PSRAM for canvas allocations. */
93
- psram?: 'opi' | 'quad';
94
- /** HAL pin numbers the program reads with adc.* — scanned from the emitted
95
- * `__tc_adc<N>_setup()` calls at compile time. Only the overlay generator
96
- * consumes this (to rewrite the ADC node's pinctrl-0 to the used channels
97
- * on SoCs that need pad muxing, e.g. STM32); prj.conf ignores it. */
98
- adcReadPins?: readonly number[];
99
- /** HAL pin numbers the program drives with dac.* — scanned from the
100
- * emitted lazy-setup guards (__tc_dact<pin>_done) at compile time. Only
101
- * the overlay generator consumes this (the DAC node's pinctrl-0 lists
102
- * the used channels); prj.conf ignores it. */
103
- dacWritePins?: readonly number[];
104
- /** HAL pin numbers the program drives with pwm.* — scanned from the
105
- * emitted `__tc_pwm_*` spec references at compile time. Only the overlay
106
- * generator consumes this (synthesized pwm-leds consumers + aliases are
107
- * emitted per used pin, so the DT carries no dead channels); prj.conf
108
- * ignores it. */
109
- pwmUsedPins?: readonly number[];
110
- /** Controller indexes per bus the program actually drives — scanned from
111
- * the emitted `__tc_<bus><N>_dev` state blocks at compile time. The
112
- * overlay enables only those controllers (an enabled-but-unused
113
- * controller claims its default pins — e.g. i2c0's GP4/GP5 on the Pico —
114
- * which a program using the OTHER controller may want as GPIO). Absent
115
- * (prepare-time overlays, driver-API-only users like the display
116
- * adapter) → every declared controller is enabled, preserving the old
117
- * behavior. prj.conf ignores these. */
118
- i2cUsedInstances?: readonly number[];
119
- spiUsedInstances?: readonly number[];
120
- uartUsedInstances?: readonly number[];
121
- }
122
-
123
- /**
124
- * Resolve the Kconfig symbol→value map for a prj.conf. Returns a Map preserving
125
- * insertion order (callers join with '\n'). Core GPIO + C++ + workqueue symbols
126
- * are always present; driver symbols are usage-gated.
127
- */
128
- export function resolveKconfigFragments(
129
- usage: KconfigUsage,
130
- debug: boolean,
131
- ): Map<string, string> {
132
- const m = new Map<string, string>();
133
-
134
- // Core driver + console.
135
- m.set('CONFIG_GPIO', 'y');
136
- m.set('CONFIG_PRINTK', 'y');
137
- m.set('CONFIG_PRINTK_SYNC', 'y');
138
- m.set('CONFIG_CONSOLE', 'y');
139
-
140
- if (usage.usesAdc) m.set('CONFIG_ADC', 'y');
141
- // Sensors: only the umbrella — each in-tree driver is `default y` on its
142
- // DT_HAS_<COMPAT>_ENABLED, so the overlay's child node enables the driver.
143
- if (usage.usesSensor) m.set('CONFIG_SENSOR', 'y');
144
- // The rare driver that is NOT DT-default-on: the catalog records its
145
- // Kconfig lines (from the driver's own Kconfig) and they are applied for
146
- // exactly the parts the program constructs. Empty for every in-tree part
147
- // today — this path is insurance for the next exception.
148
- applySensorKconfigExceptions(m, usage.sensorParts);
149
- // Float formatting: cbprintf builds float conversions only under
150
- // FP_SUPPORT, which depends on the COMPLETE implementation — without these,
151
- // %f silently prints garbage instead of the value.
152
- if (usage.usesFloatFormat) {
153
- m.set('CONFIG_CBPRINTF_COMPLETE', 'y');
154
- m.set('CONFIG_CBPRINTF_FP_SUPPORT', 'y');
155
- }
156
- if (usage.usesPwm) m.set('CONFIG_PWM', 'y');
157
- if (usage.usesDac) m.set('CONFIG_DAC', 'y');
158
- if (usage.usesI2c) m.set('CONFIG_I2C', 'y');
159
- if (usage.usesSpi) m.set('CONFIG_SPI', 'y');
160
- // USB CDC-ACM serial: the "next" USB device stack + the CDC-ACM class.
161
- // Symbol names verified against Zephyr 4.3 (subsys/usb/device_next/Kconfig):
162
- // - USB_DEVICE_STACK_NEXT is the new stack (selects UDC_DRIVER);
163
- // USB_DEVICE_STACK is the LEGACY stack — deprecated in 4.3 (selects
164
- // DEPRECATED) and its assignment is what trips the build.
165
- // - USBD_CDC_ACM_CLASS is the class (note the USBD_ prefix — a bare
166
- // CDC_ACM_CLASS is an undefined symbol). It depends on SERIAL +
167
- // DT_HAS_ZEPHYR_CDC_ACM_UART_ENABLED (the overlay's cdc-acm-uart node)
168
- // and selects UART_INTERRUPT_DRIVEN/RING_BUFFER itself; assigning it
169
- // keeps prj.conf explicit.
170
- // - UART_LINE_CTRL gates the driver's line_ctrl_get — usb.connected()
171
- // polls DTR through it.
172
- if (usage.usesUsb) {
173
- m.set('CONFIG_USB_DEVICE_STACK_NEXT', 'y');
174
- m.set('CONFIG_USBD_CDC_ACM_CLASS', 'y');
175
- m.set('CONFIG_UART_LINE_CTRL', 'y');
176
- m.set('CONFIG_SERIAL', 'y');
177
- // The device presents a serial-number string descriptor sourced from
178
- // hwinfo (the SoC's unique ID). Without it Windows keys the CDC devnode
179
- // on the physical USB port: replugs reuse stale nodes and repeated flash
180
- // cycles wedge them into permanent "access denied" opens. A serial makes
181
- // the instance path identity-based — stable across ports, immune to the
182
- // port-keyed ghost pool.
183
- m.set('CONFIG_HWINFO', 'y');
184
- }
185
- if (usage.usesWdt) m.set('CONFIG_WATCHDOG', 'y');
186
- // STM32: keep the debugger attachable in sleep/stop (see the shim's
187
- // DBGMCU init — this covers the Zephyr-side DBG_STOP bit via LL headers).
188
- if (usage.usesStm32DebugSleep) m.set('CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP', 'y');
189
- // Hardware timers via the counter driver.
190
- if (usage.usesHwtimer) m.set('CONFIG_COUNTER', 'y');
191
- if (usage.usesDisplay) {
192
- m.set('CONFIG_DISPLAY', 'y');
193
- m.set('CONFIG_SPI', 'y');
194
- m.set('CONFIG_MIPI_DBI', 'y');
195
- // Enable GDMA so the ESP32 SPI driver uses DMA for panel transfers instead
196
- // of PIO through the 64-byte hardware FIFO. Without DMA a full 480x320 fill
197
- // takes ~110ms (effectively ~4MHz); with DMA the same transfer runs at the
198
- // configured SPI clock (~80MHz) and drops into the low tens of ms. The
199
- // display overlay pairs this with dma-enabled + dmas on the spi2 node.
200
- m.set('CONFIG_DMA', 'y');
201
- // Disable the MIPI DBI SPI bridge + in-tree panel drivers (ILI9341,
202
- // ST7796S). The display adapter drives the panel directly via spi_write.
203
- // Binding these drivers would allocate a tearing-effect GPIO interrupt
204
- // that conflicts with the SPI/I2C driver interrupts — the
205
- // VECDESC_FL_SHARED assertion crashes on touch. ILI9341 matters as much
206
- // as the bridge: the driver auto-defaults on from the overlay's
207
- // ilitek,ili9341 node and references the (disabled) mipi-dbi-spi
208
- // controller's device struct, failing at link time with
209
- // "undefined reference to __device_dts_ord_N". (Assign the prompted
210
- // ILI9341, not the hidden ILI9XXX — promptless symbols reject prj.conf
211
- // assignments.)
212
- m.set('CONFIG_MIPI_DBI_SPI', 'n');
213
- m.set('CONFIG_ILI9341', 'n');
214
- m.set('CONFIG_ST7796S', 'n');
215
- }
216
- if (usage.usesTouch) {
217
- // FT6336U touch is on I2C; the XPT2046 shares the display's SPI bus.
218
- // CONFIG_INPUT stays off either way: the adapters drive the controllers
219
- // directly, and enabling it would build the in-tree input drivers
220
- // (ft5336 / xpt2046) against nodes these adapters already own.
221
- if (usage.touchController === 'xpt2046') {
222
- m.set('CONFIG_SPI', 'y');
223
- } else {
224
- m.set('CONFIG_I2C', 'y');
225
- }
226
- }
227
- // PSRAM: enable the ESP SPIRAM driver + route malloc/heap to external RAM so
228
- // large canvas allocations (scroll viewports, lists) can use PSRAM instead of
229
- // failing in internal SRAM. Zephyr's ESP32 PSRAM support uses CONFIG_ESP_SPIRAM
230
- // (not CONFIG_SPIRAM — that's an ESP-IDF symbol). The mode choice selects the
231
- // PSRAM type: OCT for OPI (ESP32-S3), QUAD for quad-spi. CONFIG_ESP_SPIRAM
232
- // selects SHARED_MULTI_HEAP automatically, which routes heap_caps_malloc to
233
- // PSRAM. The SoC dtsi already carries the psram0 DT node.
234
- if (usage.psram) {
235
- m.set('CONFIG_ESP_SPIRAM', 'y');
236
- if (usage.psram === 'opi') {
237
- m.set('CONFIG_SPIRAM_MODE_OCT', 'y');
238
- } else {
239
- m.set('CONFIG_SPIRAM_MODE_QUAD', 'y');
240
- }
241
- }
242
- if (usage.usesWifi) {
243
- // Master networking switch — every CONFIG_NET_* symbol depends on NETWORKING
244
- // (without it, Kconfig silently forces them all to n).
245
- m.set('CONFIG_NETWORKING', 'y');
246
- m.set('CONFIG_WIFI', 'y');
247
- m.set('CONFIG_WIFI_ESP32', 'y'); // family-wide ESP32 driver (esp32/s3/c3/c6)
248
- m.set('CONFIG_NET_L2_ETHERNET', 'y');
249
- m.set('CONFIG_NET_IPV4', 'y');
250
- m.set('CONFIG_NET_UDP', 'y'); // transitive dep of NET_DHCPV4
251
- m.set('CONFIG_NET_DHCPV4', 'y');
252
- // NOT CONFIG_NET_CONFIG_SETTINGS: that runs net_config_init() at boot which
253
- // BLOCKS up to NET_CONFIG_INIT_TIMEOUT (default 30s) waiting for the iface
254
- // to come up — but our shim brings the iface up itself in main() (connect),
255
- // so net_config waits the full 30s, then the dual management of the same
256
- // iface crashes the driver. Our shim owns connectivity (net_mgmt connect/
257
- // disconnect + conn_mgr monitor for L4), exactly like the standalone Zephyr
258
- // WiFi samples that omit NET_CONFIG_SETTINGS.
259
- m.set('CONFIG_NET_MGMT', 'y');
260
- m.set('CONFIG_NET_MGMT_EVENT', 'y'); // required for the net_mgmt callbacks
261
- m.set('CONFIG_NET_CONNECTION_MANAGER', 'y'); // conn_mgr — the connect portability layer
262
- // Networking stack sizes. The defaults are tiny (NET_MGMT_EVENT_STACK_SIZE
263
- // is 768 on non-x86) and the WiFi connect result/event handlers run on that
264
- // stack — overflowing it freezes the chip mid-connect (silent hard fault,
265
- // no panic dump). The official Zephyr WiFi samples (samples/net/wifi/*)
266
- // bump exactly these; mirror them. MAIN_STACK 4096→5200 because esp_wifi
267
- // device init is stack-hungry and 4096 is marginal on the ESP32-S3.
268
- m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
269
- m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
270
- m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
271
- m.set('CONFIG_MAIN_STACK_SIZE', '5200');
272
- // NOTE: wifi.set_tx_power needs no Kconfig symbol. esp_wifi_set_max_tx_power
273
- // programs the radio at runtime; its ceiling is baked into the prebuilt
274
- // libphy.a / PHY init data, not a prj.conf knob. The ESP-IDF symbol
275
- // ESP_PHY_MAX_WIFI_TX_POWER lives in components/esp_phy/Kconfig, which the
276
- // Zephyr module integration does NOT source — assigning it here would abort
277
- // the build ("undefined symbol").
278
- }
279
- if (usage.usesHttp) {
280
- // HTTP/S rides on the networking stack. The shim does its own
281
- // socket/getaddrinfo/connect, so it needs the BSD socket layer + POSIX
282
- // DNS surface + the http client lib, plus the TLS sockopt layer (which
283
- // selects mbedTLS) and the DNS resolver. HTTP needs the same IP base as
284
- // WiFi, so this emits the networking primitives even when usesWifi is
285
- // false — an http-only program still has to reach the internet. Map.set
286
- // is idempotent, so overlaps with the wifi block are harmless.
287
- m.set('CONFIG_NETWORKING', 'y');
288
- m.set('CONFIG_NET_IPV4', 'y');
289
- m.set('CONFIG_NET_DHCPV4', 'y');
290
- m.set('CONFIG_NET_TCP', 'y'); // http_client_req needs a TCP socket
291
- // NET_MAX_CONTEXTS caps the network 5-tuple (socket) pool — the default 6 is
292
- // exhausted after a handful of sequential HTTP requests even when each is
293
- // closed (closed TCP contexts linger in TIME_WAIT), and socket() then returns
294
- // -EPERM. The hardware CRUD harness makes ~17 sequential requests, so raise
295
- // this well above the default.
296
- m.set('CONFIG_NET_MAX_CONTEXTS', '16');
297
- // NET_MAX_CONN is the connection-registry pool (default 8 with v4+v6), a
298
- // *separate* limit from NET_MAX_CONTEXTS. Closed TCP entries linger briefly
299
- // in the registry, so rapid sequential HTTP requests exhaust the default and
300
- // connect() then returns -EPERM. Pair it with NET_MAX_CONTEXTS so both the
301
- // socket (5-tuple) and connection-registry pools have headroom.
302
- m.set('CONFIG_NET_MAX_CONN', '16');
303
- // ZVFS_OPEN_MAX must be set above 0 or socket() returns -EPERM (errno 1): the
304
- // fd table is allocated to exactly ZVFS_OPEN_MAX entries, and the default 0
305
- // (even with the ZVFS_OPEN_ADD_SIZE_* "min" contributors) yields zero usable
306
- // descriptors. HTTP tests open/close a socket per request; give headroom over
307
- // the per-subsystem contributors (NET=6, POSIX=3).
308
- m.set('CONFIG_ZVFS_OPEN_MAX', '16');
309
- // NET_SOCKETS is the user-facing switch for the BSD socket API + ZVFS. The
310
- // shim uses bare POSIX names (connect/socket/close/getaddrinfo/freeaddrinfo)
311
- // rather than the zsock_ forms; those bare names resolve under CONFIG_POSIX_API
312
- // (the official samples/net/sockets/http_client sample sets exactly this).
313
- // Without it, <zephyr/posix/unistd.h> gates `int close(int)` behind
314
- // #ifdef CONFIG_POSIX_API and the build fails with "'close' was not declared".
315
- m.set('CONFIG_NET_SOCKETS', 'y');
316
- m.set('CONFIG_POSIX_API', 'y');
317
- m.set('CONFIG_DNS_RESOLVER', 'y'); // getaddrinfo for hostnames
318
- m.set('CONFIG_DNS_SERVER_IP_ADDRESSES', 'y');
319
- m.set('CONFIG_HTTP_CLIENT', 'y'); // <zephyr/net/http/client.h> + http_client_req
320
- // HTTPS via Zephyr socket TLS (IPPROTO_TLS_1_2 + SOL_TLS sockopts, driven by
321
- // the shim's __tc_http_open_socket https branch). NET_SOCKETS_SOCKOPT_TLS
322
- // selects mbedTLS, but its ssl layer (mbedtls_ssl_*) needs the rest of this
323
- // matrix to actually link + a working TLS 1.2 protocol + key exchange.
324
- m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
325
- m.set('CONFIG_TLS_CREDENTIALS', 'y'); // tls_credential_add for caCert()
326
- m.set('CONFIG_MBEDTLS', 'y');
327
- m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
328
- // Enable TLS 1.2 via a single ciphersuite rather than the broad
329
- // SSL_PROTO_TLS1_2 + KEY_EXCHANGE_ALL_ENABLED (the latter selects KEXes whose
330
- // PSA_WANT_* deps are unsatisfied → Kconfig abort) or a bare
331
- // SSL_PROTO_TLS1_2 (no key exchange → check_config.h "no key exchange
332
- // methods defined"). A ciphersuite is the proven path the in-tree HTTPS
333
- // samples use (samples/net/prometheus): it transitively selects
334
- // MBEDTLS_SSL_PROTO_TLS1_2 + its one key exchange + every PSA_WANT_* key/alg
335
- // that key exchange needs, with no dangling deps. ECDHE_RSA matches the test
336
- // server's RSA cert (rsa:2048) and is widely offered; add more ciphersuites
337
- // here to broaden server compatibility.
338
- m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
339
- // Ciphersuites only *depend on* X509_CRT_PARSE_C (they don't select it) —
340
- // without it sockets_tls.c compiles out mbedtls_x509_crt_parse and every
341
- // pinned-CA handshake fails with EPERM at connect (the insecure path
342
- // skips verification, so it works either way).
343
- m.set('CONFIG_MBEDTLS_X509_CRT_PARSE_C', 'y');
344
- // KNOWN LIMITATION on this Zephyr tree: pinned-CA (verified TLS) fails at
345
- // connect — the tf-psa-crypto mbedTLS needs a wider symbol matrix
346
- // (RSA public-key parse + PEM/DER glue) than the single-ciphersuite
347
- // select pulls in, and forcing the extra symbols regresses the insecure
348
- // path. insecure() HTTPS is fully verified; caCert() chain verification
349
- // stays open until the upstream matrix is mapped.
350
- // Handshake/protocol buffers allocate from the mbedTLS heap; MBEDTLS_HEAP_SIZE
351
- // must hold ~2x MBEDTLS_SSL_MAX_CONTENT_LEN plus working state. 65000 fits on
352
- // the ESP32; mbedTLS requires the full libc and PEM (not DER) cert format.
353
- m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
354
- // 2x SSL_MAX_CONTENT_LEN record buffers + CA-chain parse state +
355
- // handshake working memory. 65000 fit insecure-mode handshakes but the
356
- // pinned-CA path (verification state) overflowed → EPERM at connect.
357
- m.set('CONFIG_MBEDTLS_HEAP_SIZE', '100000');
358
- m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
359
- m.set('CONFIG_PSA_CRYPTO', 'y');
360
- m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
361
- // The http client + TLS handshake are stack-hungry; mirror the wifi bumps
362
- // (NET_*_STACK_SIZE) and keep the larger main-stack value (TLS handshake
363
- // overflows the default main stack).
364
- m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
365
- m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
366
- m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
367
- if (!usage.usesWifi) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
368
- }
369
- if (usage.usesMqtt) {
370
- // MQTT rides on the networking stack. CONFIG_MQTT_LIB selects NET_SOCKETS;
371
- // the shim getaddrinfo-resolves the broker and runs its own poll k_thread.
372
- // CONFIG_MQTT_LIB_TLS enables MQTT_TRANSPORT_SECURE + mqtt_sec_config; the
373
- // mbedTLS matrix is the same one HTTP uses (it's idempotent via Map.set).
374
- m.set('CONFIG_NETWORKING', 'y');
375
- m.set('CONFIG_NET_IPV4', 'y');
376
- m.set('CONFIG_NET_DHCPV4', 'y');
377
- m.set('CONFIG_NET_TCP', 'y');
378
- m.set('CONFIG_NET_SOCKETS', 'y');
379
- m.set('CONFIG_MQTT_LIB', 'y');
380
- m.set('CONFIG_MQTT_LIB_TLS', 'y');
381
- // The shim getaddrinfo-resolves the broker host — without the DNS
382
- // resolver linked, getaddrinfo fails numeric AND hostname lookups with
383
- // EAI_FAIL even while plain HTTP (which selects DNS via its own block)
384
- // works.
385
- m.set('CONFIG_DNS_RESOLVER', 'y');
386
- m.set('CONFIG_DNS_SERVER_IP_ADDRESSES', 'y');
387
- // NET_MAX_CONTEXTS / NET_MAX_CONN: same exhaustion risk as HTTP — sequential
388
- // connections linger after close. Give the broker session + headroom.
389
- m.set('CONFIG_NET_MAX_CONTEXTS', '16');
390
- m.set('CONFIG_NET_MAX_CONN', '16');
391
- m.set('CONFIG_ZVFS_OPEN_MAX', '16');
392
- // mbedTLS matrix for mqtts:// (mirrors the HTTP block; overlaps are harmless).
393
- m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
394
- m.set('CONFIG_TLS_CREDENTIALS', 'y');
395
- m.set('CONFIG_MBEDTLS', 'y');
396
- m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
397
- m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
398
- // Ciphersuites only *depend on* X509_CRT_PARSE_C (they don't select it) —
399
- // without it sockets_tls.c compiles out mbedtls_x509_crt_parse and every
400
- // pinned-CA handshake fails with EPERM at connect (the insecure path
401
- // skips verification, so it works either way).
402
- m.set('CONFIG_MBEDTLS_X509_CRT_PARSE_C', 'y');
403
- // KNOWN LIMITATION on this Zephyr tree: pinned-CA (verified TLS) fails at
404
- // connect — the tf-psa-crypto mbedTLS needs a wider symbol matrix
405
- // (RSA public-key parse + PEM/DER glue) than the single-ciphersuite
406
- // select pulls in, and forcing the extra symbols regresses the insecure
407
- // path. insecure() HTTPS is fully verified; caCert() chain verification
408
- // stays open until the upstream matrix is mapped.
409
- m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
410
- // 2x SSL_MAX_CONTENT_LEN record buffers + CA-chain parse state +
411
- // handshake working memory. 65000 fit insecure-mode handshakes but the
412
- // pinned-CA path (verification state) overflowed → EPERM at connect.
413
- m.set('CONFIG_MBEDTLS_HEAP_SIZE', '100000');
414
- m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
415
- m.set('CONFIG_PSA_CRYPTO', 'y');
416
- m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
417
- m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
418
- m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
419
- if (!usage.usesWifi && !usage.usesHttp) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
420
- }
421
- if (usage.usesBle) {
422
- m.set('CONFIG_BT', 'y');
423
- m.set('CONFIG_BT_PERIPHERAL', 'y');
424
- m.set('CONFIG_BT_GATT_DYNAMIC_DB', 'y');
425
- }
426
- // Preferences: ZMS-backed settings. CONFIG_SETTINGS_ZMS depends on ZMS +
427
- // FLASH_MAP (it does NOT select them), so all three must be set explicitly.
428
- // The backend locates the storage_partition fixed-partition automatically
429
- // (or the /chosen zephyr,settings-partition — see dt-config/overlay.ts); no
430
- // partition macro is needed in the shim. ZMS is preferred over NVS per the
431
- // Zephyr docs ("as of 4.1 the recommended backend is NVS or ZMS").
432
- if (usage.usesPreferences) {
433
- m.set('CONFIG_FLASH', 'y');
434
- m.set('CONFIG_FLASH_MAP', 'y');
435
- m.set('CONFIG_ZMS', 'y');
436
- m.set('CONFIG_SETTINGS', 'y');
437
- m.set('CONFIG_SETTINGS_ZMS', 'y');
438
- }
439
- // Filesystem: littlefs on the storage partition. CONFIG_FILE_SYSTEM_LITTLEFS
440
- // selects the littlefs backend but NOT FLASH/FLASH_MAP (the partition lookup
441
- // needs them), so all three are set explicitly — same shape as the
442
- // preferences/ZMS block. The overlay points the storage_partition at the FS
443
- // (see dt-config/overlay.ts). NOTE: a program using BOTH fs.* and
444
- // preferences.* shares the one storage_partition between littlefs and ZMS —
445
- // dedicate separate partitions if both are needed (the manifest flags this).
446
- if (usage.usesFS) {
447
- m.set('CONFIG_FLASH', 'y');
448
- m.set('CONFIG_FLASH_MAP', 'y');
449
- m.set('CONFIG_FILE_SYSTEM', 'y');
450
- m.set('CONFIG_FILE_SYSTEM_LITTLEFS', 'y');
451
- // fs_mkfs (the lazy first-use format in the shim) is gated behind this —
452
- // without it the mount path links fine but the format call is undefined.
453
- m.set('CONFIG_FILE_SYSTEM_MKFS', 'y');
454
- }
455
- // usesUart: the board enables the console UART by default; the overlay (not
456
- // Kconfig) is where a UART node would be enabled, so no symbol here.
457
- // Random: <zephyr/random/random.h> sys_rand_get is backed by the random
458
- // generator subsystem's RNG_GENERATOR_CHOICE. There is no umbrella symbol —
459
- // CONFIG_RANDOM_GENERATOR is a phantom in 4.x (assigning an undefined symbol
460
- // aborts the build). The choice defaults to the entropy-device generator
461
- // when the board has a driver (ENTROPY_HAS_DRIVER: nRF52840, ESP32, …);
462
- // RNG-less SoCs (STM32F411 has no hardware RNG) need
463
- // CONFIG_TEST_RANDOM_GENERATOR to unlock the timer-clock fallback, which
464
- // the choice then picks by default. Setting both keys gets the best
465
- // available source per board.
466
- if (usage.usesRandom) {
467
- m.set('CONFIG_ENTROPY_GENERATOR', 'y');
468
- m.set('CONFIG_TEST_RANDOM_GENERATOR', 'y');
469
- }
470
-
471
- // System workqueue — bumped for worker-offload AND timer callbacks. The
472
- // workqueue itself is unconditionally built (no CONFIG_SYSTEM_WORKQUEUE symbol
473
- // exists in Zephyr — that was a phantom that broke real builds); only the
474
- // stack size is a real Kconfig knob.
475
- m.set('CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE', '8192');
476
-
477
- // C++ support.
478
- m.set('CONFIG_CPP', 'y');
479
- m.set('CONFIG_NEWLIB_LIBC', 'y');
480
- m.set('CONFIG_REQUIRES_FULL_LIBCPP', 'y');
481
- m.set('CONFIG_STD_CPP14', 'y');
482
-
483
- // Main thread stack. WiFi already bumps this to 5200 (esp_wifi device init
484
- // is stack-hungry); HTTP/MQTT + TLS also bump it (the mbedTLS handshake is
485
- // stack-hungry). The UI runtime (ui_tick) renders a large node tree with AA
486
- // text + canvas compositing per frame, so 4096 is marginal headroom and we
487
- // bump to 8192 for displays. NOTE: a Zephyr panic dump that prints
488
- // `EXCCAUSE 63` is NOT necessarily a stack overflow — on the Zephyr Xtensa
489
- // port EXCCAUSE 63 is the reserved software-exception used for k_oops/abort,
490
- // and the ESP32 port reaches it via abort() in intc_esp32.c (the
491
- // esp_intr_noniram_disable/enable unbalanced-flag guards). Resolve the dump's
492
- // PC against the .elf (xtensa_arch_except → abort) before treating it as a
493
- // stack overflow; 8192 is kept here because deep ui_tick call nesting still
494
- // wants the headroom.
495
- if (!usage.usesWifi && !usage.usesHttp && !usage.usesMqtt) {
496
- m.set('CONFIG_MAIN_STACK_SIZE', usage.usesDisplay ? '8192' : '4096');
497
- }
498
-
499
- if (debug) {
500
- m.set('CONFIG_DEBUG', 'y');
501
- m.set('CONFIG_DEBUG_OPTIMIZATIONS', 'y');
502
- }
503
-
504
- return m;
505
- }
1
+ import { SENSOR_PART_INFO } from '@typecad/hal';
2
+
3
+ /** A sensor part's catalog facts, as the exceptions helper consumes them. */
4
+ interface SensorKconfigSource {
5
+ kconfig: readonly string[];
6
+ }
7
+
8
+ /**
9
+ * Apply the per-part Kconfig exceptions the generated catalog records, for
10
+ * exactly the parts the program constructs. Lines are CONFIG_<SYM>=<value>
11
+ * strings parsed into the map; a symbol the user set explicitly in
12
+ * zephyr.kconfig is left alone (scaffold.ts already skips user overrides for
13
+ * auto symbols, and Map.set idempotence covers the rest). The lookup is a
14
+ * parameter so tests can drive it with a synthetic catalog.
15
+ */
16
+ export function applySensorKconfigExceptions(
17
+ m: Map<string, string>,
18
+ parts: readonly { part: string }[] | undefined,
19
+ lookup: Readonly<Record<string, SensorKconfigSource>> = SENSOR_PART_INFO,
20
+ ): void {
21
+ if (!parts) return;
22
+ for (const sp of parts) {
23
+ for (const line of lookup[sp.part]?.kconfig ?? []) {
24
+ const eq = line.indexOf('=');
25
+ if (eq <= 0) continue;
26
+ m.set(line.slice(0, eq), line.slice(eq + 1));
27
+ }
28
+ }
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Kconfig fragment resolver — extracts the prj.conf symbol logic
33
+ //
34
+ // Previously inline in scaffold.ts as token-scanning of the emitted source.
35
+ // Formalized here as a typed, unit-testable function keyed off a usage-analysis
36
+ // object (the same ctx.analysis.usesX flags the strategy uses). scaffold.ts
37
+ // calls this instead of inlining the scan, keeping prj.conf generation honest
38
+ // and testable.
39
+ // ---------------------------------------------------------------------------
40
+
41
+ export interface KconfigUsage {
42
+ usesAdc?: boolean;
43
+ usesPwm?: boolean;
44
+ usesDac?: boolean;
45
+ usesFS?: boolean;
46
+ usesHwtimer?: boolean;
47
+ usesI2c?: boolean;
48
+ /** I2C target mode used (i2c.resp_* ops — the responder). Enables the
49
+ * i2c_target API paths: driver implementations are gated on it (e.g.
50
+ * STM32 compiles target support + selects interrupt mode only under
51
+ * I2C_TARGET); controllers without target support still build and
52
+ * return -ENOSYS at registration, which the shim prints loudly. */
53
+ usesI2cTarget?: boolean;
54
+ usesSpi?: boolean;
55
+ usesUart?: boolean;
56
+ /** USB CDC-ACM serial used (usb.* ops). Selects the "next" USB device
57
+ * stack + CDC class; the class instances themselves are composed in the
58
+ * DT overlay and auto-default on once the node exists (assigning the
59
+ * class symbol keeps prj.conf explicit and survives DT-only probes). */
60
+ usesUsb?: boolean;
61
+ usesWdt?: boolean;
62
+ usesBle?: boolean;
63
+ usesDisplay?: boolean;
64
+ /** How the UI adapter reaches the panel. 'zephyr-display' binds the
65
+ * in-tree drivers (CONFIG_MIPI_DBI_SPI on); 'direct-spi' (default)
66
+ * force-disables them — the adapter drives the panel itself. */
67
+ displayTransport?: 'direct-spi' | 'zephyr-display';
68
+ /** Panel controller for the display Kconfig symbol when the transport
69
+ * needs one explicitly. Synthesized (compatible-driven) profiles carry
70
+ * none — their driver symbol auto-defaults on from the DT node
71
+ * (`default y depends on DT_HAS_<COMPATIBLE>_ENABLED`), so no symbol
72
+ * is assigned at all. */
73
+ displayController?: 'st7796s' | 'ili9341' | 'ssd16xx' | 'uc81xx';
74
+ /** Panel bus family from the binding harvest: i2c-family panels (mono
75
+ * OLEDs) get I2C and no SPI/MIPI/DMA block at all. */
76
+ displayBus?: 'i2c' | 'spi';
77
+ /** Profile-required Kconfig fragments, appended verbatim (e.g. the SDL
78
+ * panel's mono pixel-format choice on native_sim). */
79
+ displayKconfigExtra?: readonly string[];
80
+ usesWifi?: boolean;
81
+ usesHttp?: boolean;
82
+ usesMqtt?: boolean;
83
+ usesPreferences?: boolean;
84
+ usesRandom?: boolean;
85
+ /** A printf-family format specifier with a float conversion (%f, %.2f, %e,
86
+ * …) appears in the emitted source — Zephyr's cbprintf only links float
87
+ * conversions with FP_SUPPORT (which itself needs the COMPLETE impl). */
88
+ usesFloatFormat?: boolean;
89
+ /** DT-bound sensor parts used (sensor.* ops — the generic catalog). The
90
+ * umbrella under which every driver sensor Kconfig lives (`if SENSOR`);
91
+ * the per-driver symbols default on from their DT node presence. */
92
+ usesSensor?: boolean;
93
+ /** Addressable LED strip (strip.* ops → ws2812-spi child). */
94
+ usesStrip?: boolean;
95
+ /** Strips the program drives: SPI controller index → chain length.
96
+ * Scanned from the emitted shim's buffer declarations. */
97
+ strips?: Array<{ index: number; count: number }>;
98
+ /** USB HID keyboard/mouse (hid.* ops → zephyr,hid-device node). */
99
+ usesHid?: boolean;
100
+ /** Which HID protocol the program drives — picks the DT node's
101
+ * protocol-code and the report descriptor. */
102
+ hidProtocol?: 'keyboard' | 'mouse';
103
+ /** GPIO key matrix (matrix.* ops → gpio-kbd-matrix node). */
104
+ usesMatrix?: boolean;
105
+ /** The matrix's construction pad lists (from the lowering marker). */
106
+ matrix?: { rows: number[]; cols: number[] };
107
+ /** Explicit power-state entry (power.* ops → sys_poweroff). */
108
+ usesPower?: boolean;
109
+ /** Wall-clock time (clock.* ops → rtc alias). */
110
+ usesClock?: boolean;
111
+ /** CAN bus (can.* ops → the harvested can@ controller). */
112
+ usesCan?: boolean;
113
+ /** I2S audio (i2s.* ops → the harvested i2s@ controller). */
114
+ usesI2s?: boolean;
115
+ /** CAN loopback constructed (CAN_MODE_LOOPBACK in the emission) — the
116
+ * overlay routes both TWAI functions onto one pad (no transceiver). */
117
+ canLoopback?: boolean;
118
+ /** The counter the rtc shim wraps (label + optional timer parent for
119
+ * the child-form ESP32 counters). Omitted when the board ships its own
120
+ * rtc alias — the lowering uses the hardware node. */
121
+ clockShimCounter?: { label: string; parent?: string };
122
+ /** Distinct constructed sensors — only the overlay generator consumes this
123
+ * (one DT child node per entry, on the given I2C controller index); prj.conf
124
+ * ignores it. Same adcReadPins/pwmUsedPins pattern. */
125
+ sensorParts?: readonly { part: string; busIndex: number; port: number; busKind: 'i2c' | 'spi' | 'w1'; spiHz?: number; spiMode?: number; alertPin?: number; resolution?: number }[];
126
+ /** Distinct thin SPI targets (hal/spi-target.ts) — one DT child node per
127
+ * entry (no compatible — a raw spi_dt_spec peer), appended after sensor
128
+ * CS entries in the controller's merged cs-gpios. */
129
+ spiTargets?: readonly { busIndex: number; cs: number; hz?: number; mode?: number }[];
130
+ /** Touch controller referenced (UI touch adapter emits DT_NODELABEL(ft6336u)
131
+ * or DT_NODELABEL(xpt2046)). Selects the bus driver the node needs. */
132
+ usesTouch?: boolean;
133
+ /** Which touch controller the program uses — FT6336U rides I2C, XPT2046
134
+ * rides the display's SPI bus. Only meaningful with usesTouch. */
135
+ touchController?: 'ft6336u' | 'xpt2046';
136
+ /** The emitted shim carries the STM32F4 DBGMCU keep-SWD-alive init
137
+ * (__tc_stm32_dbgmcu token). Selects Zephyr's own "debugger attach in
138
+ * stop/sleep" init (sets DBG_STOP via the LL headers); the shim's raw
139
+ * register poke additionally covers DBG_SLEEP, which the Zephyr F4 path
140
+ * does not set. */
141
+ usesStm32DebugSleep?: boolean;
142
+ /** PSRAM type ('opi' | 'quad') when the target board has PSRAM. Emits the
143
+ * CONFIG_SPIRAM symbols so the ESP heap serves PSRAM for canvas allocations. */
144
+ psram?: 'opi' | 'quad';
145
+ /** HAL pin numbers the program reads with adc.* — scanned from the emitted
146
+ * `__tc_adc<N>_setup()` calls at compile time. Only the overlay generator
147
+ * consumes this (to rewrite the ADC node's pinctrl-0 to the used channels
148
+ * on SoCs that need pad muxing, e.g. STM32); prj.conf ignores it. */
149
+ adcReadPins?: readonly number[];
150
+ /** HAL pin numbers the program drives with dac.* — scanned from the
151
+ * emitted lazy-setup guards (__tc_dact<pin>_done) at compile time. Only
152
+ * the overlay generator consumes this (the DAC node's pinctrl-0 lists
153
+ * the used channels); prj.conf ignores it. */
154
+ dacWritePins?: readonly number[];
155
+ /** HAL pin numbers the program drives with pwm.* — scanned from the
156
+ * emitted `__tc_pwm_*` spec references at compile time. Only the overlay
157
+ * generator consumes this (synthesized pwm-leds consumers + aliases are
158
+ * emitted per used pin, so the DT carries no dead channels); prj.conf
159
+ * ignores it. */
160
+ pwmUsedPins?: readonly number[];
161
+ /** Controller indexes per bus the program actually drives — scanned from
162
+ * the emitted `__tc_<bus><N>_dev` state blocks at compile time. The
163
+ * overlay enables only those controllers (an enabled-but-unused
164
+ * controller claims its default pins — e.g. i2c0's GP4/GP5 on the Pico —
165
+ * which a program using the OTHER controller may want as GPIO). Absent
166
+ * (prepare-time overlays, driver-API-only users like the display
167
+ * adapter) → every declared controller is enabled, preserving the old
168
+ * behavior. prj.conf ignores these. */
169
+ i2cUsedInstances?: readonly number[];
170
+ spiUsedInstances?: readonly number[];
171
+ uartUsedInstances?: readonly number[];
172
+ }
173
+
174
+ /**
175
+ * Resolve the Kconfig symbol→value map for a prj.conf. Returns a Map preserving
176
+ * insertion order (callers join with '\n'). Core GPIO + C++ + workqueue symbols
177
+ * are always present; driver symbols are usage-gated.
178
+ */
179
+ export function resolveKconfigFragments(
180
+ usage: KconfigUsage,
181
+ debug: boolean,
182
+ ): Map<string, string> {
183
+ const m = new Map<string, string>();
184
+
185
+ // Core driver + console.
186
+ m.set('CONFIG_GPIO', 'y');
187
+ m.set('CONFIG_PRINTK', 'y');
188
+ m.set('CONFIG_PRINTK_SYNC', 'y');
189
+ m.set('CONFIG_CONSOLE', 'y');
190
+
191
+ if (usage.usesAdc) m.set('CONFIG_ADC', 'y');
192
+ // Sensors: only the umbrella — each in-tree driver is `default y` on its
193
+ // DT_HAS_<COMPAT>_ENABLED, so the overlay's child node enables the driver.
194
+ if (usage.usesSensor) m.set('CONFIG_SENSOR', 'y');
195
+ // 1-Wire masters: the w1 core + the bit-banged GPIO driver (default-on
196
+ // via the synthesized node, but the core W1 menuconfig gates the
197
+ // drivers — set it explicitly so the menu default cannot hide it).
198
+ if ((usage.sensorParts ?? []).some((sp) => sp.busKind === 'w1')) m.set('CONFIG_W1', 'y');
199
+ // The rare driver that is NOT DT-default-on: the catalog records its
200
+ // Kconfig lines (from the driver's own Kconfig) and they are applied for
201
+ // exactly the parts the program constructs. Empty for every in-tree part
202
+ // today — this path is insurance for the next exception.
203
+ applySensorKconfigExceptions(m, usage.sensorParts);
204
+ // Float formatting: cbprintf builds float conversions only under
205
+ // FP_SUPPORT, which depends on the COMPLETE implementation — without these,
206
+ // %f silently prints garbage instead of the value.
207
+ if (usage.usesFloatFormat) {
208
+ m.set('CONFIG_CBPRINTF_COMPLETE', 'y');
209
+ m.set('CONFIG_CBPRINTF_FP_SUPPORT', 'y');
210
+ }
211
+ if (usage.usesPwm) m.set('CONFIG_PWM', 'y');
212
+ if (usage.usesStrip) m.set('CONFIG_LED_STRIP', 'y');
213
+ // HID rides the same "next" device stack as CDC — the class is selected by
214
+ // the zephyr,hid-device node the overlay synthesizes.
215
+ if (usage.usesHid) {
216
+ m.set('CONFIG_USB_DEVICE_STACK_NEXT', 'y');
217
+ m.set('CONFIG_USBD_HID_SUPPORT', 'y');
218
+ }
219
+ if (usage.usesMatrix) {
220
+ m.set('CONFIG_INPUT', 'y');
221
+ m.set('CONFIG_INPUT_GPIO_KBD_MATRIX', 'y');
222
+ }
223
+ // sys_poweroff() only exists when the kernel builds the poweroff service
224
+ // (the SoC hook compiles under this symbol — without it the link fails
225
+ // with an undefined reference).
226
+ if (usage.usesPower) m.set('CONFIG_POWEROFF', 'y');
227
+ // The rtc subsystem; the counter shim driver defaults on when the
228
+ // synthesized zephyr,rtc-counter node exists. RTC_INIT_PRIORITY defaults
229
+ // to KERNEL_INIT_PRIORITY_DEVICE (=50) which is BELOW the counter driver
230
+ // (=60) — the shim asserts it inits after its parent counter, so raise
231
+ // it whenever the shim is in play.
232
+ if (usage.usesCan) m.set('CONFIG_CAN', 'y');
233
+ // I2S needs the DMA umbrella + the ESP32 GDMA driver (the controller
234
+ // DMA-runs through the gdma node the overlay enables — the driver is
235
+ // default-y once the node is on, but the umbrella needs the explicit
236
+ // set because nothing else pulls it in).
237
+ if (usage.usesI2s) {
238
+ m.set('CONFIG_I2S', 'y');
239
+ m.set('CONFIG_DMA', 'y');
240
+ }
241
+ if (usage.usesClock) {
242
+ m.set('CONFIG_RTC', 'y');
243
+ m.set('CONFIG_RTC_INIT_PRIORITY', '70');
244
+ }
245
+ if (usage.usesDac) m.set('CONFIG_DAC', 'y');
246
+ if (usage.usesI2c) m.set('CONFIG_I2C', 'y');
247
+ if (usage.usesI2cTarget) m.set('CONFIG_I2C_TARGET', 'y');
248
+ if (usage.usesSpi) m.set('CONFIG_SPI', 'y');
249
+ // USB CDC-ACM serial: the "next" USB device stack + the CDC-ACM class.
250
+ // Symbol names verified against Zephyr 4.3 (subsys/usb/device_next/Kconfig):
251
+ // - USB_DEVICE_STACK_NEXT is the new stack (selects UDC_DRIVER);
252
+ // USB_DEVICE_STACK is the LEGACY stack — deprecated in 4.3 (selects
253
+ // DEPRECATED) and its assignment is what trips the build.
254
+ // - USBD_CDC_ACM_CLASS is the class (note the USBD_ prefix — a bare
255
+ // CDC_ACM_CLASS is an undefined symbol). It depends on SERIAL +
256
+ // DT_HAS_ZEPHYR_CDC_ACM_UART_ENABLED (the overlay's cdc-acm-uart node)
257
+ // and selects UART_INTERRUPT_DRIVEN/RING_BUFFER itself; assigning it
258
+ // keeps prj.conf explicit.
259
+ // - UART_LINE_CTRL gates the driver's line_ctrl_get — usb.connected()
260
+ // polls DTR through it.
261
+ if (usage.usesUsb) {
262
+ m.set('CONFIG_USB_DEVICE_STACK_NEXT', 'y');
263
+ m.set('CONFIG_USBD_CDC_ACM_CLASS', 'y');
264
+ m.set('CONFIG_UART_LINE_CTRL', 'y');
265
+ m.set('CONFIG_SERIAL', 'y');
266
+ // The device presents a serial-number string descriptor sourced from
267
+ // hwinfo (the SoC's unique ID). Without it Windows keys the CDC devnode
268
+ // on the physical USB port: replugs reuse stale nodes and repeated flash
269
+ // cycles wedge them into permanent "access denied" opens. A serial makes
270
+ // the instance path identity-based — stable across ports, immune to the
271
+ // port-keyed ghost pool.
272
+ m.set('CONFIG_HWINFO', 'y');
273
+ }
274
+ if (usage.usesWdt) m.set('CONFIG_WATCHDOG', 'y');
275
+ // STM32: keep the debugger attachable in sleep/stop (see the shim's
276
+ // DBGMCU init — this covers the Zephyr-side DBG_STOP bit via LL headers).
277
+ if (usage.usesStm32DebugSleep) m.set('CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP', 'y');
278
+ // Hardware timers via the counter driver.
279
+ if (usage.usesHwtimer) m.set('CONFIG_COUNTER', 'y');
280
+ if (usage.usesDisplay) {
281
+ m.set('CONFIG_DISPLAY', 'y');
282
+ if (usage.displayBus === 'i2c' || usage.displayKconfigExtra) {
283
+ // Self-describing panels — no SPI bridge, no DMA:
284
+ // - i2c-family (mono OLEDs, ssd1306-class): an I2C child node; the
285
+ // panel driver self-builds from it.
286
+ // - board-provided panels (native_sim's sdl_dc): the board's own DTS
287
+ // wires the display; the profile's extra fragments (e.g. the SDL
288
+ // panel's mono pixel-format choice) land here verbatim.
289
+ if (usage.displayBus === 'i2c') m.set('CONFIG_I2C', 'y');
290
+ for (const frag of usage.displayKconfigExtra ?? []) {
291
+ const eq = frag.indexOf('=');
292
+ if (eq > 0) m.set(frag.slice(0, eq).trim(), frag.slice(eq + 1).trim());
293
+ }
294
+ } else {
295
+ m.set('CONFIG_SPI', 'y');
296
+ m.set('CONFIG_MIPI_DBI', 'y');
297
+ // Enable GDMA so the ESP32 SPI driver uses DMA for panel transfers instead
298
+ // of PIO through the 64-byte hardware FIFO. Without DMA a full 480x320 fill
299
+ // takes ~110ms (effectively ~4MHz); with DMA the same transfer runs at the
300
+ // configured SPI clock (~80MHz) and drops into the low tens of ms. The
301
+ // display overlay pairs this with dma-enabled + dmas on the spi2 node.
302
+ m.set('CONFIG_DMA', 'y');
303
+ if (usage.displayTransport === 'zephyr-display') {
304
+ // Native display-API transport: the mipi-dbi SPI bridge hosts the
305
+ // panel driver, which owns init/rotation/wire format. Controller-
306
+ // specific exceptions and the drop-in (compatible-driven) path:
307
+ // - st7796s (registry profile): the stock bridge deasserts CS
308
+ // between command and parameters (scrambles clone panels), so it
309
+ // stays OFF — the adapter emits an app-local CS-holding host.
310
+ // - ili9341 (registry profile): assign the prompted symbol explicitly
311
+ // (survives DT-only probes; not the hidden ILI9XXX).
312
+ // - synthesized profiles (no controller): assign NOTHING — panel
313
+ // drivers are `default y depends on DT_HAS_<COMPATIBLE>_ENABLED`,
314
+ // so the overlay's enabled node builds its own driver.
315
+ if (usage.displayController === 'st7796s') {
316
+ m.set('CONFIG_MIPI_DBI_SPI', 'n');
317
+ m.set('CONFIG_ST7796S', 'y');
318
+ } else if (usage.displayController === 'ili9341') {
319
+ m.set('CONFIG_MIPI_DBI_SPI', 'y');
320
+ m.set('CONFIG_ILI9341', 'y');
321
+ } else {
322
+ m.set('CONFIG_MIPI_DBI_SPI', 'y');
323
+ }
324
+ // E-ink families (Stage 4): the panel driver under the mipi-dbi SPI
325
+ // bridge — ssd16xx/uc81xx own the flash-cycle refresh + BUSY wait.
326
+ if (usage.displayController === 'ssd16xx') {
327
+ m.set('CONFIG_SSD16XX', 'y');
328
+ } else if (usage.displayController === 'uc81xx') {
329
+ m.set('CONFIG_UC81XX', 'y');
330
+ }
331
+ } else {
332
+ // Direct-spi transport (default): the display adapter drives the panel
333
+ // directly via spi_write. Disable the MIPI DBI SPI bridge + in-tree panel
334
+ // drivers (ILI9341, ST7796S) — binding them would allocate a
335
+ // tearing-effect GPIO interrupt that conflicts with the SPI/I2C driver
336
+ // interrupts — the VECDESC_FL_SHARED assertion crashes on touch.
337
+ // ILI9341 matters as much as the bridge: the driver auto-defaults on
338
+ // from the overlay's ilitek,ili9341 node and references the (disabled)
339
+ // mipi-dbi-spi controller's device struct, failing at link time with
340
+ // "undefined reference to __device_dts_ord_N".
341
+ m.set('CONFIG_MIPI_DBI_SPI', 'n');
342
+ m.set('CONFIG_ILI9341', 'n');
343
+ m.set('CONFIG_ST7796S', 'n');
344
+ }
345
+ }
346
+ }
347
+ if (usage.usesTouch) {
348
+ if (usage.touchController === 'xpt2046') {
349
+ // XPT2046 rides the display's SPI bus — the raw adapter owns the chip
350
+ // (CONFIG_INPUT stays off: the in-tree driver's DT min/max scaling
351
+ // would double-apply the runtime calibration — see touch-adapter.ts).
352
+ m.set('CONFIG_SPI', 'y');
353
+ } else {
354
+ // FT6336U through the input subsystem: the in-tree focaltech driver
355
+ // owns the controller in polling mode (no int-gpios on the node — no
356
+ // GPIO IRQ is registered, sidestepping the ESP32 VECDESC_FL_SHARED
357
+ // crash the interrupt path hit). The adapter listens for its events.
358
+ m.set('CONFIG_INPUT', 'y');
359
+ m.set('CONFIG_INPUT_FT5336', 'y');
360
+ m.set('CONFIG_I2C', 'y');
361
+ }
362
+ }
363
+ // PSRAM: enable the ESP SPIRAM driver + route malloc/heap to external RAM so
364
+ // large canvas allocations (scroll viewports, lists) can use PSRAM instead of
365
+ // failing in internal SRAM. Zephyr's ESP32 PSRAM support uses CONFIG_ESP_SPIRAM
366
+ // (not CONFIG_SPIRAM — that's an ESP-IDF symbol). The mode choice selects the
367
+ // PSRAM type: OCT for OPI (ESP32-S3), QUAD for quad-spi. CONFIG_ESP_SPIRAM
368
+ // selects SHARED_MULTI_HEAP automatically, which routes heap_caps_malloc to
369
+ // PSRAM. The SoC dtsi already carries the psram0 DT node.
370
+ if (usage.psram) {
371
+ m.set('CONFIG_ESP_SPIRAM', 'y');
372
+ if (usage.psram === 'opi') {
373
+ m.set('CONFIG_SPIRAM_MODE_OCT', 'y');
374
+ } else {
375
+ m.set('CONFIG_SPIRAM_MODE_QUAD', 'y');
376
+ }
377
+ }
378
+ if (usage.usesWifi) {
379
+ // Master networking switch — every CONFIG_NET_* symbol depends on NETWORKING
380
+ // (without it, Kconfig silently forces them all to n).
381
+ m.set('CONFIG_NETWORKING', 'y');
382
+ m.set('CONFIG_WIFI', 'y');
383
+ m.set('CONFIG_WIFI_ESP32', 'y'); // family-wide ESP32 driver (esp32/s3/c3/c6)
384
+ m.set('CONFIG_NET_L2_ETHERNET', 'y');
385
+ m.set('CONFIG_NET_IPV4', 'y');
386
+ m.set('CONFIG_NET_UDP', 'y'); // transitive dep of NET_DHCPV4
387
+ m.set('CONFIG_NET_DHCPV4', 'y');
388
+ // NOT CONFIG_NET_CONFIG_SETTINGS: that runs net_config_init() at boot which
389
+ // BLOCKS up to NET_CONFIG_INIT_TIMEOUT (default 30s) waiting for the iface
390
+ // to come up — but our shim brings the iface up itself in main() (connect),
391
+ // so net_config waits the full 30s, then the dual management of the same
392
+ // iface crashes the driver. Our shim owns connectivity (net_mgmt connect/
393
+ // disconnect + conn_mgr monitor for L4), exactly like the standalone Zephyr
394
+ // WiFi samples that omit NET_CONFIG_SETTINGS.
395
+ m.set('CONFIG_NET_MGMT', 'y');
396
+ m.set('CONFIG_NET_MGMT_EVENT', 'y'); // required for the net_mgmt callbacks
397
+ m.set('CONFIG_NET_CONNECTION_MANAGER', 'y'); // conn_mgr — the connect portability layer
398
+ // Networking stack sizes. The defaults are tiny (NET_MGMT_EVENT_STACK_SIZE
399
+ // is 768 on non-x86) and the WiFi connect result/event handlers run on that
400
+ // stack — overflowing it freezes the chip mid-connect (silent hard fault,
401
+ // no panic dump). The official Zephyr WiFi samples (samples/net/wifi/*)
402
+ // bump exactly these; mirror them. MAIN_STACK 4096→5200 because esp_wifi
403
+ // device init is stack-hungry and 4096 is marginal on the ESP32-S3.
404
+ m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
405
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
406
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
407
+ m.set('CONFIG_MAIN_STACK_SIZE', '5200');
408
+ // NOTE: wifi.set_tx_power needs no Kconfig symbol. esp_wifi_set_max_tx_power
409
+ // programs the radio at runtime; its ceiling is baked into the prebuilt
410
+ // libphy.a / PHY init data, not a prj.conf knob. The ESP-IDF symbol
411
+ // ESP_PHY_MAX_WIFI_TX_POWER lives in components/esp_phy/Kconfig, which the
412
+ // Zephyr module integration does NOT source — assigning it here would abort
413
+ // the build ("undefined symbol").
414
+ }
415
+ if (usage.usesHttp) {
416
+ // HTTP/S rides on the networking stack. The shim does its own
417
+ // socket/getaddrinfo/connect, so it needs the BSD socket layer + POSIX
418
+ // DNS surface + the http client lib, plus the TLS sockopt layer (which
419
+ // selects mbedTLS) and the DNS resolver. HTTP needs the same IP base as
420
+ // WiFi, so this emits the networking primitives even when usesWifi is
421
+ // false — an http-only program still has to reach the internet. Map.set
422
+ // is idempotent, so overlaps with the wifi block are harmless.
423
+ m.set('CONFIG_NETWORKING', 'y');
424
+ m.set('CONFIG_NET_IPV4', 'y');
425
+ m.set('CONFIG_NET_DHCPV4', 'y');
426
+ m.set('CONFIG_NET_TCP', 'y'); // http_client_req needs a TCP socket
427
+ // NET_MAX_CONTEXTS caps the network 5-tuple (socket) pool — the default 6 is
428
+ // exhausted after a handful of sequential HTTP requests even when each is
429
+ // closed (closed TCP contexts linger in TIME_WAIT), and socket() then returns
430
+ // -EPERM. The hardware CRUD harness makes ~17 sequential requests, so raise
431
+ // this well above the default.
432
+ m.set('CONFIG_NET_MAX_CONTEXTS', '16');
433
+ // NET_MAX_CONN is the connection-registry pool (default 8 with v4+v6), a
434
+ // *separate* limit from NET_MAX_CONTEXTS. Closed TCP entries linger briefly
435
+ // in the registry, so rapid sequential HTTP requests exhaust the default and
436
+ // connect() then returns -EPERM. Pair it with NET_MAX_CONTEXTS so both the
437
+ // socket (5-tuple) and connection-registry pools have headroom.
438
+ m.set('CONFIG_NET_MAX_CONN', '16');
439
+ // ZVFS_OPEN_MAX must be set above 0 or socket() returns -EPERM (errno 1): the
440
+ // fd table is allocated to exactly ZVFS_OPEN_MAX entries, and the default 0
441
+ // (even with the ZVFS_OPEN_ADD_SIZE_* "min" contributors) yields zero usable
442
+ // descriptors. HTTP tests open/close a socket per request; give headroom over
443
+ // the per-subsystem contributors (NET=6, POSIX=3).
444
+ m.set('CONFIG_ZVFS_OPEN_MAX', '16');
445
+ // NET_SOCKETS is the user-facing switch for the BSD socket API + ZVFS. The
446
+ // shim uses bare POSIX names (connect/socket/close/getaddrinfo/freeaddrinfo)
447
+ // rather than the zsock_ forms; those bare names resolve under CONFIG_POSIX_API
448
+ // (the official samples/net/sockets/http_client sample sets exactly this).
449
+ // Without it, <zephyr/posix/unistd.h> gates `int close(int)` behind
450
+ // #ifdef CONFIG_POSIX_API and the build fails with "'close' was not declared".
451
+ m.set('CONFIG_NET_SOCKETS', 'y');
452
+ m.set('CONFIG_POSIX_API', 'y');
453
+ m.set('CONFIG_DNS_RESOLVER', 'y'); // getaddrinfo for hostnames
454
+ m.set('CONFIG_DNS_SERVER_IP_ADDRESSES', 'y');
455
+ m.set('CONFIG_HTTP_CLIENT', 'y'); // <zephyr/net/http/client.h> + http_client_req
456
+ // HTTPS via Zephyr socket TLS (IPPROTO_TLS_1_2 + SOL_TLS sockopts, driven by
457
+ // the shim's __tc_http_open_socket https branch). NET_SOCKETS_SOCKOPT_TLS
458
+ // selects mbedTLS, but its ssl layer (mbedtls_ssl_*) needs the rest of this
459
+ // matrix to actually link + a working TLS 1.2 protocol + key exchange.
460
+ m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
461
+ m.set('CONFIG_TLS_CREDENTIALS', 'y'); // tls_credential_add for caCert()
462
+ m.set('CONFIG_MBEDTLS', 'y');
463
+ m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
464
+ // Enable TLS 1.2 via a single ciphersuite rather than the broad
465
+ // SSL_PROTO_TLS1_2 + KEY_EXCHANGE_ALL_ENABLED (the latter selects KEXes whose
466
+ // PSA_WANT_* deps are unsatisfied → Kconfig abort) or a bare
467
+ // SSL_PROTO_TLS1_2 (no key exchange → check_config.h "no key exchange
468
+ // methods defined"). A ciphersuite is the proven path the in-tree HTTPS
469
+ // samples use (samples/net/prometheus): it transitively selects
470
+ // MBEDTLS_SSL_PROTO_TLS1_2 + its one key exchange + every PSA_WANT_* key/alg
471
+ // that key exchange needs, with no dangling deps. ECDHE_RSA matches the test
472
+ // server's RSA cert (rsa:2048) and is widely offered; add more ciphersuites
473
+ // here to broaden server compatibility.
474
+ m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
475
+ // Ciphersuites only *depend on* X509_CRT_PARSE_C (they don't select it) —
476
+ // without it sockets_tls.c compiles out mbedtls_x509_crt_parse and every
477
+ // pinned-CA handshake fails with EPERM at connect (the insecure path
478
+ // skips verification, so it works either way).
479
+ m.set('CONFIG_MBEDTLS_X509_CRT_PARSE_C', 'y');
480
+ // KNOWN LIMITATION on this Zephyr tree: pinned-CA (verified TLS) was
481
+ // observed failing at connect with EPERM — the tf-psa-crypto mbedTLS
482
+ // needs a wider symbol matrix (RSA public-key parse + PEM/DER glue)
483
+ // than the single-ciphersuite select pulls in, and forcing the extra
484
+ // symbols regressed the insecure path (two hardware cycles proved it
485
+ // upstream). CAVEAT: that diagnosis predates the 2026-09 fix of the
486
+ // request-fact reset ordering (every option — including caCert — was
487
+ // being wiped before each send, which alone reproduces connect
488
+ // failures); the matrix limitation needs a hardware re-run to confirm
489
+ // it still applies. insecure() HTTPS and no-CA https are unaffected.
490
+ // Handshake/protocol buffers allocate from the mbedTLS heap; MBEDTLS_HEAP_SIZE
491
+ // must hold ~2x MBEDTLS_SSL_MAX_CONTENT_LEN plus working state. 65000 fits on
492
+ // the ESP32; mbedTLS requires the full libc and PEM (not DER) cert format.
493
+ m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
494
+ // 2x SSL_MAX_CONTENT_LEN record buffers + CA-chain parse state +
495
+ // handshake working memory. 65000 fit insecure-mode handshakes but the
496
+ // pinned-CA path (verification state) overflowed → EPERM at connect.
497
+ m.set('CONFIG_MBEDTLS_HEAP_SIZE', '100000');
498
+ m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
499
+ m.set('CONFIG_PSA_CRYPTO', 'y');
500
+ m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
501
+ // The http client + TLS handshake are stack-hungry; mirror the wifi bumps
502
+ // (NET_*_STACK_SIZE) and keep the larger main-stack value (TLS handshake
503
+ // overflows the default main stack).
504
+ m.set('CONFIG_NET_MGMT_EVENT_STACK_SIZE', '4096');
505
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
506
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
507
+ if (!usage.usesWifi) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
508
+ }
509
+ if (usage.usesMqtt) {
510
+ // MQTT rides on the networking stack. CONFIG_MQTT_LIB selects NET_SOCKETS;
511
+ // the shim getaddrinfo-resolves the broker and runs its own poll k_thread.
512
+ // CONFIG_MQTT_LIB_TLS enables MQTT_TRANSPORT_SECURE + mqtt_sec_config; the
513
+ // mbedTLS matrix is the same one HTTP uses (it's idempotent via Map.set).
514
+ m.set('CONFIG_NETWORKING', 'y');
515
+ m.set('CONFIG_NET_IPV4', 'y');
516
+ m.set('CONFIG_NET_DHCPV4', 'y');
517
+ m.set('CONFIG_NET_TCP', 'y');
518
+ m.set('CONFIG_NET_SOCKETS', 'y');
519
+ m.set('CONFIG_MQTT_LIB', 'y');
520
+ m.set('CONFIG_MQTT_LIB_TLS', 'y');
521
+ // The shim getaddrinfo-resolves the broker host — without the DNS
522
+ // resolver linked, getaddrinfo fails numeric AND hostname lookups with
523
+ // EAI_FAIL even while plain HTTP (which selects DNS via its own block)
524
+ // works.
525
+ m.set('CONFIG_DNS_RESOLVER', 'y');
526
+ m.set('CONFIG_DNS_SERVER_IP_ADDRESSES', 'y');
527
+ // NET_MAX_CONTEXTS / NET_MAX_CONN: same exhaustion risk as HTTP — sequential
528
+ // connections linger after close. Give the broker session + headroom.
529
+ m.set('CONFIG_NET_MAX_CONTEXTS', '16');
530
+ m.set('CONFIG_NET_MAX_CONN', '16');
531
+ m.set('CONFIG_ZVFS_OPEN_MAX', '16');
532
+ // mbedTLS matrix for mqtts:// (mirrors the HTTP block; overlaps are harmless).
533
+ m.set('CONFIG_NET_SOCKETS_SOCKOPT_TLS', 'y');
534
+ m.set('CONFIG_TLS_CREDENTIALS', 'y');
535
+ m.set('CONFIG_MBEDTLS', 'y');
536
+ m.set('CONFIG_MBEDTLS_BUILTIN', 'y');
537
+ m.set('CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'y');
538
+ // Ciphersuites only *depend on* X509_CRT_PARSE_C (they don't select it) —
539
+ // without it sockets_tls.c compiles out mbedtls_x509_crt_parse and every
540
+ // pinned-CA handshake fails with EPERM at connect (the insecure path
541
+ // skips verification, so it works either way).
542
+ m.set('CONFIG_MBEDTLS_X509_CRT_PARSE_C', 'y');
543
+ // KNOWN LIMITATION on this Zephyr tree: pinned-CA (verified TLS) was
544
+ // observed failing at connect with EPERM — the tf-psa-crypto mbedTLS
545
+ // needs a wider symbol matrix (RSA public-key parse + PEM/DER glue)
546
+ // than the single-ciphersuite select pulls in. CAVEAT: that diagnosis
547
+ // was made on the HTTPS path and predates the 2026-09 request-fact
548
+ // reset-ordering fix (options were wiped before every send, which
549
+ // alone reproduces connect failures); it is shared here because mqtts
550
+ // pins its CA through the same matrix — re-verify on hardware.
551
+ m.set('CONFIG_MBEDTLS_ENABLE_HEAP', 'y');
552
+ // 2x SSL_MAX_CONTENT_LEN record buffers + CA-chain parse state +
553
+ // handshake working memory. 65000 fit insecure-mode handshakes but the
554
+ // pinned-CA path (verification state) overflowed → EPERM at connect.
555
+ m.set('CONFIG_MBEDTLS_HEAP_SIZE', '100000');
556
+ m.set('CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN', '16384');
557
+ m.set('CONFIG_PSA_CRYPTO', 'y');
558
+ m.set('CONFIG_REQUIRES_FULL_LIBC', 'y');
559
+ m.set('CONFIG_NET_TX_STACK_SIZE', '2048');
560
+ m.set('CONFIG_NET_RX_STACK_SIZE', '2048');
561
+ if (!usage.usesWifi && !usage.usesHttp) m.set('CONFIG_MAIN_STACK_SIZE', '5200');
562
+ }
563
+ if (usage.usesBle) {
564
+ m.set('CONFIG_BT', 'y');
565
+ m.set('CONFIG_BT_PERIPHERAL', 'y');
566
+ m.set('CONFIG_BT_GATT_DYNAMIC_DB', 'y');
567
+ }
568
+ // Preferences: ZMS-backed settings. CONFIG_SETTINGS_ZMS depends on ZMS +
569
+ // FLASH_MAP (it does NOT select them), so all three must be set explicitly.
570
+ // The backend locates the storage_partition fixed-partition automatically
571
+ // (or the /chosen zephyr,settings-partition — see dt-config/overlay.ts); no
572
+ // partition macro is needed in the shim. ZMS is preferred over NVS per the
573
+ // Zephyr docs ("as of 4.1 the recommended backend is NVS or ZMS").
574
+ if (usage.usesPreferences) {
575
+ m.set('CONFIG_FLASH', 'y');
576
+ m.set('CONFIG_FLASH_MAP', 'y');
577
+ m.set('CONFIG_ZMS', 'y');
578
+ m.set('CONFIG_SETTINGS', 'y');
579
+ m.set('CONFIG_SETTINGS_ZMS', 'y');
580
+ }
581
+ // Filesystem: littlefs on the storage partition. CONFIG_FILE_SYSTEM_LITTLEFS
582
+ // selects the littlefs backend but NOT FLASH/FLASH_MAP (the partition lookup
583
+ // needs them), so all three are set explicitly — same shape as the
584
+ // preferences/ZMS block. The overlay points the storage_partition at the FS
585
+ // (see dt-config/overlay.ts). NOTE: a program using BOTH fs.* and
586
+ // preferences.* shares the one storage_partition between littlefs and ZMS —
587
+ // dedicate separate partitions if both are needed (the manifest flags this).
588
+ if (usage.usesFS) {
589
+ m.set('CONFIG_FLASH', 'y');
590
+ m.set('CONFIG_FLASH_MAP', 'y');
591
+ m.set('CONFIG_FILE_SYSTEM', 'y');
592
+ m.set('CONFIG_FILE_SYSTEM_LITTLEFS', 'y');
593
+ // fs_mkfs (the lazy first-use format in the shim) is gated behind this —
594
+ // without it the mount path links fine but the format call is undefined.
595
+ m.set('CONFIG_FILE_SYSTEM_MKFS', 'y');
596
+ }
597
+ // usesUart: the board enables the console UART by default; the overlay (not
598
+ // Kconfig) is where a UART node would be enabled, so no symbol here.
599
+ // Random: <zephyr/random/random.h> sys_rand_get is backed by the random
600
+ // generator subsystem's RNG_GENERATOR_CHOICE. There is no umbrella symbol —
601
+ // CONFIG_RANDOM_GENERATOR is a phantom in 4.x (assigning an undefined symbol
602
+ // aborts the build). The choice defaults to the entropy-device generator
603
+ // when the board has a driver (ENTROPY_HAS_DRIVER: nRF52840, ESP32, …);
604
+ // RNG-less SoCs (STM32F411 has no hardware RNG) need
605
+ // CONFIG_TEST_RANDOM_GENERATOR to unlock the timer-clock fallback, which
606
+ // the choice then picks by default. Setting both keys gets the best
607
+ // available source per board.
608
+ if (usage.usesRandom) {
609
+ m.set('CONFIG_ENTROPY_GENERATOR', 'y');
610
+ m.set('CONFIG_TEST_RANDOM_GENERATOR', 'y');
611
+ }
612
+
613
+ // System workqueue — bumped for worker-offload AND timer callbacks. The
614
+ // workqueue itself is unconditionally built (no CONFIG_SYSTEM_WORKQUEUE symbol
615
+ // exists in Zephyr — that was a phantom that broke real builds); only the
616
+ // stack size is a real Kconfig knob.
617
+ m.set('CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE', '8192');
618
+
619
+ // C++ support.
620
+ m.set('CONFIG_CPP', 'y');
621
+ m.set('CONFIG_NEWLIB_LIBC', 'y');
622
+ m.set('CONFIG_REQUIRES_FULL_LIBCPP', 'y');
623
+ m.set('CONFIG_STD_CPP14', 'y');
624
+
625
+ // Main thread stack. WiFi already bumps this to 5200 (esp_wifi device init
626
+ // is stack-hungry); HTTP/MQTT + TLS also bump it (the mbedTLS handshake is
627
+ // stack-hungry). The UI runtime (ui_tick) renders a large node tree with AA
628
+ // text + canvas compositing per frame, so 4096 is marginal headroom and we
629
+ // bump to 8192 for displays. NOTE: a Zephyr panic dump that prints
630
+ // `EXCCAUSE 63` is NOT necessarily a stack overflow — on the Zephyr Xtensa
631
+ // port EXCCAUSE 63 is the reserved software-exception used for k_oops/abort,
632
+ // and the ESP32 port reaches it via abort() in intc_esp32.c (the
633
+ // esp_intr_noniram_disable/enable unbalanced-flag guards). Resolve the dump's
634
+ // PC against the .elf (xtensa_arch_except → abort) before treating it as a
635
+ // stack overflow; 8192 is kept here because deep ui_tick call nesting still
636
+ // wants the headroom.
637
+ if (!usage.usesWifi && !usage.usesHttp && !usage.usesMqtt) {
638
+ m.set('CONFIG_MAIN_STACK_SIZE', usage.usesDisplay ? '8192' : '4096');
639
+ }
640
+
641
+ if (debug) {
642
+ m.set('CONFIG_DEBUG', 'y');
643
+ m.set('CONFIG_DEBUG_OPTIMIZATIONS', 'y');
644
+ }
645
+
646
+ return m;
647
+ }