@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (191) hide show
  1. package/README.md +22 -35
  2. package/dist/as-built.d.ts +33 -0
  3. package/dist/as-built.js +188 -0
  4. package/dist/async/timer-polyfill.js +1 -1
  5. package/dist/boardgen.d.ts +128 -0
  6. package/dist/boardgen.js +1366 -0
  7. package/dist/chips/board-overrides.d.ts +7 -0
  8. package/dist/chips/board-overrides.js +11 -0
  9. package/dist/chips/controllers.d.ts +28 -8
  10. package/dist/chips/controllers.js +49 -12
  11. package/dist/chips/esp32s3.js +49 -0
  12. package/dist/chips/index.d.ts +6 -12
  13. package/dist/chips/index.js +18 -34
  14. package/dist/chips/resolve.d.ts +16 -2
  15. package/dist/chips/resolve.js +298 -15
  16. package/dist/chips/soc/.d.ts +2 -0
  17. package/dist/chips/soc/.js +129 -0
  18. package/dist/chips/soc/esp32.d.ts +2 -0
  19. package/dist/chips/soc/esp32.js +120 -0
  20. package/dist/chips/soc/esp32c3.d.ts +2 -0
  21. package/dist/chips/soc/esp32c3.js +90 -0
  22. package/dist/chips/soc/esp32c6.d.ts +2 -0
  23. package/dist/chips/soc/esp32c6.js +109 -0
  24. package/dist/chips/soc/esp32s3.d.ts +2 -0
  25. package/dist/chips/soc/esp32s3.js +189 -0
  26. package/dist/chips/soc/index.d.ts +2 -0
  27. package/dist/chips/soc/index.js +23 -0
  28. package/dist/chips/soc/nrf52840.d.ts +2 -0
  29. package/dist/chips/soc/nrf52840.js +130 -0
  30. package/dist/chips/soc/rp2040.d.ts +2 -0
  31. package/dist/chips/soc/rp2040.js +141 -0
  32. package/dist/chips/soc/rp2350a.d.ts +2 -0
  33. package/dist/chips/soc/rp2350a.js +145 -0
  34. package/dist/chips/soc/samd21g18a.d.ts +2 -0
  35. package/dist/chips/soc/samd21g18a.js +143 -0
  36. package/dist/chips/soc/stm32f411xe.d.ts +2 -0
  37. package/dist/chips/soc/stm32f411xe.js +251 -0
  38. package/dist/chips/types.d.ts +382 -12
  39. package/dist/chips/xiao-ble.js +33 -0
  40. package/dist/display/profiles.js +1 -1
  41. package/dist/doctor.js +38 -4
  42. package/dist/dt-config/custom-board.d.ts +23 -0
  43. package/dist/dt-config/custom-board.js +227 -0
  44. package/dist/dt-config/kconfig.d.ts +83 -1
  45. package/dist/dt-config/kconfig.js +116 -27
  46. package/dist/dt-config/overlay.js +623 -18
  47. package/dist/framework.manifest.d.ts +20 -30
  48. package/dist/framework.manifest.js +124 -178
  49. package/dist/index.d.ts +4 -1
  50. package/dist/index.js +8 -2
  51. package/dist/lowering/adc.d.ts +26 -5
  52. package/dist/lowering/adc.js +137 -31
  53. package/dist/lowering/ble.js +31 -28
  54. package/dist/lowering/dac.js +9 -9
  55. package/dist/lowering/fs.js +18 -10
  56. package/dist/lowering/gpio.d.ts +2 -0
  57. package/dist/lowering/gpio.js +90 -10
  58. package/dist/lowering/http.js +39 -14
  59. package/dist/lowering/hwtimer.d.ts +4 -0
  60. package/dist/lowering/hwtimer.js +35 -10
  61. package/dist/lowering/i2c.js +46 -54
  62. package/dist/lowering/index.d.ts +5 -5
  63. package/dist/lowering/index.js +15 -16
  64. package/dist/lowering/interrupts.d.ts +22 -5
  65. package/dist/lowering/interrupts.js +104 -22
  66. package/dist/lowering/mqtt.js +12 -6
  67. package/dist/lowering/power.js +0 -4
  68. package/dist/lowering/preferences.js +49 -77
  69. package/dist/lowering/pulse.js +5 -36
  70. package/dist/lowering/pwm.d.ts +32 -3
  71. package/dist/lowering/pwm.js +105 -17
  72. package/dist/lowering/random.js +3 -3
  73. package/dist/lowering/sensor.d.ts +37 -0
  74. package/dist/lowering/sensor.js +119 -0
  75. package/dist/lowering/spi.d.ts +12 -0
  76. package/dist/lowering/spi.js +65 -40
  77. package/dist/lowering/thread.d.ts +14 -0
  78. package/dist/lowering/thread.js +55 -0
  79. package/dist/lowering/timing.js +20 -46
  80. package/dist/lowering/tone.js +18 -5
  81. package/dist/lowering/uart.d.ts +13 -0
  82. package/dist/lowering/uart.js +72 -51
  83. package/dist/lowering/usb.d.ts +34 -0
  84. package/dist/lowering/usb.js +206 -0
  85. package/dist/lowering/wdt.d.ts +1 -1
  86. package/dist/lowering/wdt.js +17 -7
  87. package/dist/lowering/wifi.d.ts +3 -2
  88. package/dist/lowering/wifi.js +120 -111
  89. package/dist/sdk/board-catalog-sync.d.ts +16 -0
  90. package/dist/sdk/board-catalog-sync.js +41 -0
  91. package/dist/sdk/board-data.generated.d.ts +2 -0
  92. package/dist/sdk/board-data.generated.js +4 -0
  93. package/dist/sdk/catalog-walker.d.ts +90 -0
  94. package/dist/sdk/catalog-walker.js +682 -0
  95. package/dist/sdk/dts-reader.d.ts +83 -0
  96. package/dist/sdk/dts-reader.js +596 -0
  97. package/dist/strategy.d.ts +136 -17
  98. package/dist/strategy.js +881 -212
  99. package/dist/toolchain/bossac-touch.d.ts +18 -0
  100. package/dist/toolchain/bossac-touch.js +88 -0
  101. package/dist/toolchain/compat.d.ts +2 -2
  102. package/dist/toolchain/compat.js +8 -3
  103. package/dist/toolchain/debug-config.d.ts +21 -26
  104. package/dist/toolchain/debug-config.js +212 -72
  105. package/dist/toolchain/env-check.d.ts +4 -4
  106. package/dist/toolchain/env-check.js +5 -5
  107. package/dist/toolchain/index.d.ts +66 -1
  108. package/dist/toolchain/index.js +713 -58
  109. package/dist/toolchain/scaffold.d.ts +9 -0
  110. package/dist/toolchain/scaffold.js +84 -7
  111. package/dist/toolchain/west-discover.d.ts +8 -1
  112. package/dist/toolchain/west-discover.js +19 -6
  113. package/dist/toolchain/west-spawn.js +17 -5
  114. package/installer/CHANGELOG.md +473 -0
  115. package/installer/README.md +328 -0
  116. package/installer/environment.yml +46 -0
  117. package/installer/etc/conda/activate.d/zephyr.bat +7 -0
  118. package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
  119. package/installer/etc/conda/activate.d/zephyr.sh +20 -0
  120. package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
  121. package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
  122. package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
  123. package/installer/install.mjs +580 -0
  124. package/installer/install.ps1 +619 -0
  125. package/installer/install.sh +310 -0
  126. package/installer/lib/detect-platform.sh +62 -0
  127. package/installer/lib/fetch-sdk.sh +304 -0
  128. package/installer/lib/init-workspace.sh +153 -0
  129. package/installer/lib/write-activation.sh +76 -0
  130. package/installer/patches/tfm-c-include-separators.patch +19 -0
  131. package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
  132. package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
  133. package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
  134. package/installer/templates/project/.vscode/settings.json +30 -0
  135. package/installer/templates/project/README.md +58 -0
  136. package/installer/versions.env +110 -0
  137. package/package.json +16 -16
  138. package/src/as-built.ts +206 -0
  139. package/src/boardgen.ts +1428 -0
  140. package/src/chips/controllers.ts +61 -12
  141. package/src/chips/index.ts +35 -54
  142. package/src/chips/resolve.ts +316 -14
  143. package/src/chips/types.ts +388 -13
  144. package/src/display/profiles.ts +1 -1
  145. package/src/doctor.ts +110 -77
  146. package/src/dt-config/custom-board.ts +325 -0
  147. package/src/dt-config/kconfig.ts +177 -19
  148. package/src/dt-config/overlay.ts +1058 -450
  149. package/src/framework.manifest.ts +125 -178
  150. package/src/index.ts +57 -41
  151. package/src/lowering/adc.ts +222 -102
  152. package/src/lowering/ble.ts +32 -28
  153. package/src/lowering/dac.ts +82 -82
  154. package/src/lowering/fs.ts +18 -10
  155. package/src/lowering/gpio.ts +242 -152
  156. package/src/lowering/http.ts +512 -488
  157. package/src/lowering/hwtimer.ts +139 -101
  158. package/src/lowering/i2c.ts +116 -126
  159. package/src/lowering/index.ts +91 -93
  160. package/src/lowering/interrupts.ts +113 -21
  161. package/src/lowering/mqtt.ts +12 -6
  162. package/src/lowering/preferences.ts +327 -354
  163. package/src/lowering/pwm.ts +167 -74
  164. package/src/lowering/random.ts +3 -3
  165. package/src/lowering/sensor.ts +155 -0
  166. package/src/lowering/spi.ts +164 -124
  167. package/src/lowering/thread.ts +63 -0
  168. package/src/lowering/timing.ts +46 -72
  169. package/src/lowering/uart.ts +73 -51
  170. package/src/lowering/usb.ts +221 -0
  171. package/src/lowering/wdt.ts +95 -84
  172. package/src/lowering/wifi.ts +120 -111
  173. package/src/sdk/board-catalog-sync.ts +62 -0
  174. package/src/strategy.ts +888 -229
  175. package/src/toolchain/bossac-touch.ts +112 -0
  176. package/src/toolchain/compat.ts +8 -3
  177. package/src/toolchain/debug-config.ts +666 -522
  178. package/src/toolchain/env-check.ts +9 -9
  179. package/src/toolchain/index.ts +765 -44
  180. package/src/toolchain/scaffold.ts +81 -7
  181. package/src/toolchain/west-discover.ts +334 -316
  182. package/src/toolchain/west-spawn.ts +17 -5
  183. package/src/async/timer-polyfill.ts +0 -107
  184. package/src/chips/esp32.ts +0 -74
  185. package/src/chips/esp32s3.ts +0 -57
  186. package/src/chips/xiao-ble.ts +0 -70
  187. package/src/lowering/power.ts +0 -70
  188. package/src/lowering/pulse.ts +0 -93
  189. package/src/lowering/tone.ts +0 -60
  190. package/src/lowering/worker-backing.ts +0 -94
  191. package/src/lowering/worker.ts +0 -17
@@ -1,354 +1,327 @@
1
- // ---------------------------------------------------------------------------
2
- // Preferences lowering — Zephyr settings subsystem (ZMS backend)
3
- //
4
- // The HAL Preferences surface is the ESP32-NVS session model: begin(ns) →
5
- // typed put_*/get_* → end(). Zephyr's settings subsystem has no session — it
6
- // is a flat dotted key-space plus a one-time settings_load() at boot that
7
- // fills an in-RAM cache via the h_set callback. We model the session on top:
8
- //
9
- // begin("app") → records the active subtree prefix "tc/app/"
10
- // putInt("k",v) → writes the cache + settings_save_one("tc/app/k", bytes, 4)
11
- // getInt("k",d) → cache lookup, returns d if absent
12
- // remove("k") → settings_delete("tc/app/k") + drop cache slot
13
- // clear() → settings_delete each known tc/<ns>/* key + clear cache
14
- // end() → clears the prefix (NOT the cache — end() is not a wipe on
15
- // ESP32 either; it only closes the NVS handle)
16
- //
17
- // begin/end are intentionally lightweight on Zephyr. They stay in the IR so a
18
- // future backend that genuinely needs a session/namespace (NVS, a crypto-
19
- // sealed store) has the hook without lowering every read through setup. On
20
- // Zephyr they only carry the key prefix.
21
- //
22
- // The settings backend is ZMS (Zephyr Memory Storage), selected by
23
- // CONFIG_SETTINGS + CONFIG_ZMS + CONFIG_SETTINGS_ZMS. The backend locates the
24
- // storage partition automatically: the fixed-partition labeled
25
- // `storage_partition`, or whatever /chosen `zephyr,settings-partition` points
26
- // at (see dt-config/overlay.ts). No partition macro is referenced here.
27
- //
28
- // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
29
- // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
30
- // ---------------------------------------------------------------------------
31
-
32
- import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
33
-
34
- /** Render a HAL field: pass through (already rendered by the resolver). */
35
- function s(v: unknown): string {
36
- return String(v);
37
- }
38
-
39
- // Type tags for the cache slot payload. Scoped enum (AUTOSAR: enum class).
40
- // The on-metal order matters: these are stored in the `type` byte and matched
41
- // at h_set time, so do not renumber.
42
- const TAG = {
43
- INT: 1,
44
- UINT: 2,
45
- BOOL: 3,
46
- FLOAT: 4,
47
- STRING: 5,
48
- } as const;
49
-
50
- /**
51
- * The preferences runtime shim. All helpers are `static`/`inline` so unused
52
- * ones don't trip -Wunused-function in the single generated TU (mirrors
53
- * wifi.ts / http.ts / mqtt.ts).
54
- *
55
- * The cache is the source of truth at runtime: settings_load() repopulates it
56
- * once at boot via h_set, and every put / get / remove reads or writes it
57
- * directly. settings_save_one and settings_delete keep flash in sync on
58
- * writes; reads never touch flash. This is the same split the ESP32
59
- * Preferences library uses (NVS is the cache, just in flash-backed RAM pages).
60
- */
61
- export function preferencesInitLines(): string[] {
62
- return [
63
- `// CUTTLEFISH_PREFS_BEGIN`,
64
- `#define __TC_PREFS_SLOT_COUNT 32`,
65
- `#define __TC_PREFS_KEY_LEN 48`,
66
- `#define __TC_PREFS_VAL_LEN 32`,
67
- `#define __TC_PREFS_STR_LEN 31`,
68
- ``,
69
- `// One cache slot. The full settings key (tc/<ns>/<key>) lives in key[] so`,
70
- `// remove()/clear() can reconstruct the exact name to settings_delete. The`,
71
- `// value is stored in its native wire form (little-endian for the numeric`,
72
- `// types) so h_set can memcpy straight from the settings read callback.`,
73
- `struct __tc_prefs_slot final {`,
74
- ` char key[__TC_PREFS_KEY_LEN];`,
75
- ` uint8_t type;`,
76
- ` uint8_t val[__TC_PREFS_VAL_LEN];`,
77
- ` uint8_t len;`,
78
- ` bool used;`,
79
- `};`,
80
- ``,
81
- `// Active namespace prefix ("tc/<ns>/"). begin() overwrites it; end() clears.`,
82
- `// prefs_ns is deliberately sized to hold "tc/" + a 16-char ns + "/" + NUL.`,
83
- `static struct {`,
84
- ` char ns[24];`,
85
- ` bool loaded;`,
86
- ` __tc_prefs_slot slots[__TC_PREFS_SLOT_COUNT];`,
87
- `} __tc_prefs;`,
88
- ``,
89
- `// ── settings_load() handler (the only mandatory callback) ───────────────`,
90
- `// settings_load() calls h_set once per stored key it finds, handing us the`,
91
- `// value via read_cb. We copy it into a cache slot keyed by the full dotted`,
92
- `// path. h_get/h_commit are unused (the cache is the post-load source of`,
93
- `// truth) so they return -ENOENT / 0 to satisfy the handler contract.`,
94
- `static int __tc_prefs_h_set(const char* name, size_t len, settings_read_cb read_cb, void* cb_arg) {`,
95
- ` (void)len;`,
96
- ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
97
- ` if (!__tc_prefs.slots[i].used) {`,
98
- ` // Stash the type byte (first val byte) by reading into the slot.`,
99
- ` ssize_t got = read_cb(cb_arg, __tc_prefs.slots[i].val, __TC_PREFS_VAL_LEN);`,
100
- ` if (got <= 0) { return static_cast<int>(got); }`,
101
- ` __tc_prefs.slots[i].len = static_cast<uint8_t>(got);`,
102
- ` // Type is encoded as the first byte of the stored value (see put_*).`,
103
- ` __tc_prefs.slots[i].type = __tc_prefs.slots[i].val[0];`,
104
- ` (void)snprintf(__tc_prefs.slots[i].key, __TC_PREFS_KEY_LEN, "%s", name);`,
105
- ` __tc_prefs.slots[i].used = true;`,
106
- ` return 0;`,
107
- ` }`,
108
- ` }`,
109
- ` return 0; // cache full — silently drop (matches ESP32 NVS-full behavior)`,
110
- `}`,
111
- `static int __tc_prefs_h_get(const char* key, char* val, int val_len_max) {`,
112
- ` (void)key; (void)val; (void)val_len_max;`,
113
- ` return -ENOENT;`,
114
- `}`,
115
- `static int __tc_prefs_h_commit(void) { return 0; }`,
116
- `SETTINGS_STATIC_HANDLER_DEFINE(tc_prefs, "tc", __tc_prefs_h_get, __tc_prefs_h_set,`,
117
- ` __tc_prefs_h_commit, NULL);`,
118
- ``,
119
- `// One-time subsystem init + load. Idempotent — the HAL may call begin()`,
120
- `// repeatedly. settings_subsys_init() is itself idempotent in Zephyr, but the`,
121
- `// loaded flag short-circuits the (relatively expensive) settings_load() walk.`,
122
- `static inline void __tc_prefs_ensure_loaded(void) {`,
123
- ` if (!__tc_prefs.loaded) {`,
124
- ` (void)settings_subsys_init();`,
125
- ` (void)settings_load();`,
126
- ` __tc_prefs.loaded = true;`,
127
- ` }`,
128
- `}`,
129
- ``,
130
- `// Build the full settings name "tc/<ns>/<key>" into out. ns is the active`,
131
- `// prefix set by begin(); the slot's key[] stores this full path so remove()`,
132
- `// and clear() can settings_delete the exact stored name.`,
133
- `static void __tc_prefs_full(char* out, size_t outsz, const char* key) {`,
134
- ` (void)snprintf(out, outsz, "%s%s", __tc_prefs.ns, key);`,
135
- `}`,
136
- ``,
137
- `// Slot lookup by full settings name. Returns nullptr when absent (the get_*`,
138
- `// helpers then fall back to the caller's default value, matching the ESP32`,
139
- `// getInt(key, default) contract).`,
140
- `static __tc_prefs_slot* __tc_prefs_find(const char* full) {`,
141
- ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
142
- ` if (__tc_prefs.slots[i].used &&`,
143
- ` strncmp(__tc_prefs.slots[i].key, full, __TC_PREFS_KEY_LEN) == 0) {`,
144
- ` return &__tc_prefs.slots[i];`,
145
- ` }`,
146
- ` }`,
147
- ` return nullptr;`,
148
- `}`,
149
- ``,
150
- `// Allocate (or reuse) a slot for a full name. Eviction is last-write on a`,
151
- `// free slot; a full cache returns nullptr and the put silently no-ops (the`,
152
- `// in-RAM value is still returned to the program on get, just not persisted).`,
153
- `static __tc_prefs_slot* __tc_prefs_slot_for(const char* full) {`,
154
- ` __tc_prefs_slot* existing = __tc_prefs_find(full);`,
155
- ` if (existing != nullptr) { return existing; }`,
156
- ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
157
- ` if (!__tc_prefs.slots[i].used) { return &__tc_prefs.slots[i]; }`,
158
- ` }`,
159
- ` return nullptr;`,
160
- `}`,
161
- ``,
162
- `// Generic typed put: stamp the type byte, stash the value, persist via`,
163
- `// settings_save_one, and mirror into the cache. The type byte rides along`,
164
- `// as val[0] so h_set can recover it on the next boot without a separate`,
165
- `// name→type map.`,
166
- `template <typename T>`,
167
- `static inline void __tc_prefs_put(const char* key, uint8_t tag, const T& value) {`,
168
- ` char full[__TC_PREFS_KEY_LEN];`,
169
- ` __tc_prefs_full(full, sizeof(full), key);`,
170
- ` uint8_t buf[1U + sizeof(T)];`,
171
- ` buf[0] = tag;`,
172
- ` (void)memcpy(&buf[1], &value, sizeof(T));`,
173
- ` (void)settings_save_one(full, buf, sizeof(buf));`,
174
- ` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
175
- ` if (slot != nullptr) {`,
176
- ` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
177
- ` slot->type = tag;`,
178
- ` slot->len = static_cast<uint8_t>(sizeof(buf));`,
179
- ` (void)memcpy(slot->val, buf, sizeof(buf));`,
180
- ` slot->used = true;`,
181
- ` }`,
182
- `}`,
183
- ``,
184
- `template <typename T>`,
185
- `static inline T __tc_prefs_get(const char* key, uint8_t tag, T def) {`,
186
- ` char full[__TC_PREFS_KEY_LEN];`,
187
- ` __tc_prefs_full(full, sizeof(full), key);`,
188
- ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
189
- ` if (slot == nullptr || slot->type != tag || slot->len < (1U + sizeof(T))) {`,
190
- ` return def;`,
191
- ` }`,
192
- ` T out;`,
193
- ` (void)memcpy(&out, &slot->val[1], sizeof(T));`,
194
- ` return out;`,
195
- `}`,
196
- ``,
197
- `// ── Lifecycle (session model on top of the flat key-space) ──────────────`,
198
- `// begin records "tc/<ns>/" as the prefix every subsequent key is built under.`,
199
- `// end clears the prefix (NOT the cache). Both are cheap on Zephyr: the real`,
200
- `// work is in settings_load() at boot. They exist for portability with the`,
201
- `// ESP32 NVS session model and so a future session-needing backend has a hook.`,
202
- `static inline void __tc_prefs_begin(const char* ns, bool readOnly) {`,
203
- ` (void)readOnly;`,
204
- ` __tc_prefs_ensure_loaded();`,
205
- ` (void)snprintf(__tc_prefs.ns, sizeof(__tc_prefs.ns), "tc/%s/", ns);`,
206
- `}`,
207
- `static inline void __tc_prefs_end(void) {`,
208
- ` __tc_prefs.ns[0] = '\\0';`,
209
- `}`,
210
- ``,
211
- `// remove: drop one key from flash and the cache. Falls back to no-op if the`,
212
- `// slot isn't cached (settings_delete on a missing key returns -ENOENT, which`,
213
- `// we swallow).`,
214
- `static inline void __tc_prefs_remove(const char* key) {`,
215
- ` char full[__TC_PREFS_KEY_LEN];`,
216
- ` __tc_prefs_full(full, sizeof(full), key);`,
217
- ` (void)settings_delete(full);`,
218
- ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
219
- ` if (slot != nullptr) { slot->used = false; }`,
220
- `}`,
221
- ``,
222
- `// clear: drop every key under the active namespace. We walk the cache (the`,
223
- `// authoritative list of keys we have written) and settings_delete each whose`,
224
- `// full name starts with the active ns prefix, then mark the slot free. Only`,
225
- `// the active namespace is cleared keys under other namespaces survive.`,
226
- `static inline void __tc_prefs_clear(void) {`,
227
- ` const size_t plen = strlen(__tc_prefs.ns);`,
228
- ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
229
- ` if (__tc_prefs.slots[i].used &&`,
230
- ` strncmp(__tc_prefs.slots[i].key, __tc_prefs.ns, plen) == 0) {`,
231
- ` (void)settings_delete(__tc_prefs.slots[i].key);`,
232
- ` __tc_prefs.slots[i].used = false;`,
233
- ` }`,
234
- ` }`,
235
- `}`,
236
- ``,
237
- `// ── Typed accessors ─────────────────────────────────────────────────────`,
238
- `// Each put_* stamps its tag byte and persists; each get_* reads the cache and`,
239
- `// returns the default on miss/type-mismatch (mirrors ESP32 Preferences).`,
240
- `static inline void __tc_prefs_put_int(const char* key, int32_t v) {`,
241
- ` __tc_prefs_put<int32_t>(key, ${TAG.INT}, v);`,
242
- `}`,
243
- `static inline int32_t __tc_prefs_get_int(const char* key, int32_t def) {`,
244
- ` return __tc_prefs_get<int32_t>(key, ${TAG.INT}, def);`,
245
- `}`,
246
- `static inline void __tc_prefs_put_uint(const char* key, uint32_t v) {`,
247
- ` __tc_prefs_put<uint32_t>(key, ${TAG.UINT}, v);`,
248
- `}`,
249
- `static inline uint32_t __tc_prefs_get_uint(const char* key, uint32_t def) {`,
250
- ` return __tc_prefs_get<uint32_t>(key, ${TAG.UINT}, def);`,
251
- `}`,
252
- `static inline void __tc_prefs_put_bool(const char* key, bool v) {`,
253
- ` uint8_t b = v ? 1U : 0U;`,
254
- ` __tc_prefs_put<uint8_t>(key, ${TAG.BOOL}, b);`,
255
- `}`,
256
- `static inline bool __tc_prefs_get_bool(const char* key, bool def) {`,
257
- ` uint8_t b = __tc_prefs_get<uint8_t>(key, ${TAG.BOOL}, def ? 1U : 0U);`,
258
- ` return b != 0U;`,
259
- `}`,
260
- `static inline void __tc_prefs_put_float(const char* key, float v) {`,
261
- ` __tc_prefs_put<float>(key, ${TAG.FLOAT}, v);`,
262
- `}`,
263
- `static inline float __tc_prefs_get_float(const char* key, float def) {`,
264
- ` return __tc_prefs_get<float>(key, ${TAG.FLOAT}, def);`,
265
- `}`,
266
- ``,
267
- `// Strings are length-prefixed into the value buffer (capped at STR_LEN so the`,
268
- `// whole slot still fits in __TC_PREFS_VAL_LEN). get copies out with a NUL and`,
269
- `// returns def when the slot is absent or the wrong type.`,
270
- `static inline void __tc_prefs_put_string(const char* key, const char* v) {`,
271
- ` char full[__TC_PREFS_KEY_LEN];`,
272
- ` __tc_prefs_full(full, sizeof(full), key);`,
273
- ` uint8_t buf[1U + __TC_PREFS_STR_LEN];`,
274
- ` buf[0] = ${TAG.STRING};`,
275
- ` size_t n = strlen(v);`,
276
- ` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
277
- ` (void)memcpy(&buf[1], v, n);`,
278
- ` (void)settings_save_one(full, buf, 1U + n);`,
279
- ` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
280
- ` if (slot != nullptr) {`,
281
- ` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
282
- ` slot->type = ${TAG.STRING};`,
283
- ` slot->len = static_cast<uint8_t>(1U + static_cast<uint8_t>(n));`,
284
- ` (void)memcpy(slot->val, buf, 1U + n);`,
285
- ` slot->used = true;`,
286
- ` }`,
287
- `}`,
288
- `static inline const char* __tc_prefs_get_string(const char* key, const char* def) {`,
289
- ` static char out[__TC_PREFS_STR_LEN + 1U];`,
290
- ` char full[__TC_PREFS_KEY_LEN];`,
291
- ` __tc_prefs_full(full, sizeof(full), key);`,
292
- ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
293
- ` if (slot == nullptr || slot->type != ${TAG.STRING} || slot->len < 1U) {`,
294
- ` return def;`,
295
- ` }`,
296
- ` size_t n = slot->len - 1U;`,
297
- ` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
298
- ` (void)memcpy(out, &slot->val[1], n);`,
299
- ` out[n] = '\\0';`,
300
- ` return out;`,
301
- `}`,
302
- `// CUTTLEFISH_PREFS_END`,
303
- ``,
304
- ];
305
- }
306
-
307
- /**
308
- * Resolve a HAL preferences.* op to Zephyr C++ via the __tc_prefs_* shim.
309
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning
310
- * ops (the get_* family). The `default` arm throws the standard unsupported-op
311
- * error so the manifest validator's per-op probe stays honest.
312
- */
313
- export function lowerPreferences(op: HALOpIR): { code?: string; expression?: string } {
314
- const o = op as any;
315
-
316
- switch (op.operation) {
317
- case 'preferences.begin':
318
- // readOnly is honored as a no-op: Zephyr settings has no read-only mount,
319
- // and silently ignoring it matches the ESP32 Preferences behavior when
320
- // an RO handle is written (the put returns an error the app doesn't see).
321
- return { code: `__tc_prefs_begin(${s(o.namespace)}, ${o.readOnly ? 'true' : 'false'});` };
322
- case 'preferences.end':
323
- return { code: `__tc_prefs_end();` };
324
- case 'preferences.clear':
325
- return { code: `__tc_prefs_clear();` };
326
- case 'preferences.remove':
327
- return { code: `__tc_prefs_remove(${s(o.key)});` };
328
- case 'preferences.put_int':
329
- return { code: `__tc_prefs_put_int(${s(o.key)}, ${s(o.value)});` };
330
- case 'preferences.get_int':
331
- return { expression: `__tc_prefs_get_int(${s(o.key)}, ${s(o.defaultValue)})` };
332
- case 'preferences.put_uint':
333
- return { code: `__tc_prefs_put_uint(${s(o.key)}, ${s(o.value)});` };
334
- case 'preferences.get_uint':
335
- return { expression: `__tc_prefs_get_uint(${s(o.key)}, ${s(o.defaultValue)})` };
336
- case 'preferences.put_bool':
337
- return { code: `__tc_prefs_put_bool(${s(o.key)}, ${o.value ? 'true' : 'false'});` };
338
- case 'preferences.get_bool':
339
- return { expression: `__tc_prefs_get_bool(${s(o.key)}, ${o.defaultValue ? 'true' : 'false'})` };
340
- case 'preferences.put_float':
341
- return { code: `__tc_prefs_put_float(${s(o.key)}, ${s(o.value)});` };
342
- case 'preferences.get_float':
343
- return { expression: `__tc_prefs_get_float(${s(o.key)}, ${s(o.defaultValue)})` };
344
- case 'preferences.put_string':
345
- return { code: `__tc_prefs_put_string(${s(o.key)}, ${s(o.value)});` };
346
- case 'preferences.get_string':
347
- return { expression: `__tc_prefs_get_string(${s(o.key)}, ${s(o.defaultValue)})` };
348
- default:
349
- throw new Error(
350
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
351
- `Open an issue or use rawCpp() to emit it manually.`,
352
- );
353
- }
354
- }
1
+ // ---------------------------------------------------------------------------
2
+ // Preferences lowering — Zephyr settings subsystem (ZMS backend)
3
+ //
4
+ // The HAL Preferences surface is the ESP32-NVS session model: begin(ns) →
5
+ // typed put_*/get_* → end(). Zephyr's settings subsystem has no session — it
6
+ // is a flat dotted key-space plus a one-time settings_load() at boot that
7
+ // fills an in-RAM cache via the h_set callback. We model the session on top:
8
+ //
9
+ // begin("app") → records the active subtree prefix "tc/app/"
10
+ // putInt("k",v) → writes the cache + settings_save_one("tc/app/k", bytes, 4)
11
+ // getInt("k",d) → cache lookup, returns d if absent
12
+ // remove("k") → settings_delete("tc/app/k") + drop cache slot
13
+ // clear() → settings_delete each known tc/<ns>/* key + clear cache
14
+ // end() → clears the prefix (NOT the cache — end() is not a wipe on
15
+ // ESP32 either; it only closes the NVS handle)
16
+ //
17
+ // begin/end are intentionally lightweight on Zephyr. They stay in the IR so a
18
+ // future backend that genuinely needs a session/namespace (NVS, a crypto-
19
+ // sealed store) has the hook without lowering every read through setup. On
20
+ // Zephyr they only carry the key prefix.
21
+ //
22
+ // The settings backend is ZMS (Zephyr Memory Storage), selected by
23
+ // CONFIG_SETTINGS + CONFIG_ZMS + CONFIG_SETTINGS_ZMS. The backend locates the
24
+ // storage partition automatically: the fixed-partition labeled
25
+ // `storage_partition`, or whatever /chosen `zephyr,settings-partition` points
26
+ // at (see dt-config/overlay.ts). No partition macro is referenced here.
27
+ //
28
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
29
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
30
+ // ---------------------------------------------------------------------------
31
+
32
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
33
+
34
+ /** Render a HAL field: pass through (already rendered by the resolver). */
35
+ function unq(v: unknown): string {
36
+ return String(v).replace(/^"|"$/g, '');
37
+ }
38
+
39
+ function s(v: unknown): string {
40
+ return String(v);
41
+ }
42
+
43
+ // Type tags for the cache slot payload. Scoped enum (AUTOSAR: enum class).
44
+ // The on-metal order matters: these are stored in the `type` byte and matched
45
+ // at h_set time, so do not renumber.
46
+ const TAG = {
47
+ INT: 1,
48
+ UINT: 2,
49
+ BOOL: 3,
50
+ FLOAT: 4,
51
+ STRING: 5,
52
+ } as const;
53
+
54
+ /**
55
+ * The preferences runtime shim. All helpers are `static`/`inline` so unused
56
+ * ones don't trip -Wunused-function in the single generated TU (mirrors
57
+ * wifi.ts / http.ts / mqtt.ts).
58
+ *
59
+ * The cache is the source of truth at runtime: settings_load() repopulates it
60
+ * once at boot via h_set, and every put / get / remove reads or writes it
61
+ * directly. settings_save_one and settings_delete keep flash in sync on
62
+ * writes; reads never touch flash. This is the same split the ESP32
63
+ * Preferences library uses (NVS is the cache, just in flash-backed RAM pages).
64
+ */
65
+ export function preferencesInitLines(): string[] {
66
+ return [
67
+ `// CUTTLEFISH_PREFS_BEGIN`,
68
+ `#define __TC_PREFS_SLOT_COUNT 32`,
69
+ `#define __TC_PREFS_KEY_LEN 48`,
70
+ `#define __TC_PREFS_VAL_LEN 32`,
71
+ `#define __TC_PREFS_STR_LEN 31`,
72
+ ``,
73
+ `// One cache slot. The full settings key (tc/<ns>/<key>) lives in key[] so`,
74
+ `// remove()/clear() can reconstruct the exact name to settings_delete. The`,
75
+ `// value is stored in its native wire form (little-endian for the numeric`,
76
+ `// types) so h_set can memcpy straight from the settings read callback.`,
77
+ `struct __tc_prefs_slot final {`,
78
+ ` char key[__TC_PREFS_KEY_LEN];`,
79
+ ` uint8_t type;`,
80
+ ` uint8_t val[__TC_PREFS_VAL_LEN];`,
81
+ ` uint8_t len;`,
82
+ ` bool used;`,
83
+ `};`,
84
+ ``,
85
+ `static struct {`,
86
+ ` bool loaded;`,
87
+ ` __tc_prefs_slot slots[__TC_PREFS_SLOT_COUNT];`,
88
+ `} __tc_prefs;`,
89
+ ``,
90
+ `// ── settings_load() handler (the only mandatory callback) ───────────────`,
91
+ `// settings_load() calls h_set once per stored key it finds, handing us the`,
92
+ `// value via read_cb. We copy it into a cache slot keyed by the full dotted`,
93
+ `// path. h_get/h_commit are unused (the cache is the post-load source of`,
94
+ `// truth) so they return -ENOENT / 0 to satisfy the handler contract.`,
95
+ `static int __tc_prefs_h_set(const char* name, size_t len, settings_read_cb read_cb, void* cb_arg) {`,
96
+ ` (void)len;`,
97
+ ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
98
+ ` if (!__tc_prefs.slots[i].used) {`,
99
+ ` // Stash the type byte (first val byte) by reading into the slot.`,
100
+ ` ssize_t got = read_cb(cb_arg, __tc_prefs.slots[i].val, __TC_PREFS_VAL_LEN);`,
101
+ ` if (got <= 0) { return static_cast<int>(got); }`,
102
+ ` __tc_prefs.slots[i].len = static_cast<uint8_t>(got);`,
103
+ ` // Type is encoded as the first byte of the stored value (see put_*).`,
104
+ ` __tc_prefs.slots[i].type = __tc_prefs.slots[i].val[0];`,
105
+ ` // h_set hands us the name RELATIVE to the handler's "tc"`,
106
+ ` // subtree ("rig/marker") — the cache keys full names, so`,
107
+ ` // re-attach the prefix or loaded entries never match a lookup.`,
108
+ ` (void)snprintf(__tc_prefs.slots[i].key, __TC_PREFS_KEY_LEN, "tc/%s", name);`,
109
+ ` __tc_prefs.slots[i].used = true;`,
110
+ ` return 0;`,
111
+ ` }`,
112
+ ` }`,
113
+ ` return 0; // cache full — silently drop (matches ESP32 NVS-full behavior)`,
114
+ `}`,
115
+ `static int __tc_prefs_h_get(const char* full, char* val, int val_len_max) {`,
116
+ ` (void)full; (void)val; (void)val_len_max;`,
117
+ ` return -ENOENT;`,
118
+ `}`,
119
+ `static int __tc_prefs_h_commit(void) { return 0; }`,
120
+ `SETTINGS_STATIC_HANDLER_DEFINE(tc_prefs, "tc", __tc_prefs_h_get, __tc_prefs_h_set,`,
121
+ ` __tc_prefs_h_commit, NULL);`,
122
+ ``,
123
+ `// One-time subsystem init + load. Idempotent — the HAL may call begin()`,
124
+ `// repeatedly. settings_subsys_init() is itself idempotent in Zephyr, but the`,
125
+ `// loaded flag short-circuits the (relatively expensive) settings_load() walk.`,
126
+ `static inline void __tc_prefs_ensure_loaded(void) {`,
127
+ ` if (!__tc_prefs.loaded) {`,
128
+ ` (void)settings_subsys_init();`,
129
+ ` (void)settings_load();`,
130
+ ` __tc_prefs.loaded = true;`,
131
+ ` }`,
132
+ `}`,
133
+ ``,
134
+ ``,
135
+ `// Slot lookup by full settings name. Returns nullptr when absent (the get_*`,
136
+ `// helpers then fall back to the caller's default value, matching the ESP32`,
137
+ `// getInt(key, default) contract).`,
138
+ `static __tc_prefs_slot* __tc_prefs_find(const char* full) {`,
139
+ ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
140
+ ` if (__tc_prefs.slots[i].used &&`,
141
+ ` strncmp(__tc_prefs.slots[i].key, full, __TC_PREFS_KEY_LEN) == 0) {`,
142
+ ` return &__tc_prefs.slots[i];`,
143
+ ` }`,
144
+ ` }`,
145
+ ` return nullptr;`,
146
+ `}`,
147
+ ``,
148
+ `// Allocate (or reuse) a slot for a full name. Eviction is last-write on a`,
149
+ `// free slot; a full cache returns nullptr and the put silently no-ops (the`,
150
+ `// in-RAM value is still returned to the program on get, just not persisted).`,
151
+ `static __tc_prefs_slot* __tc_prefs_slot_for(const char* full) {`,
152
+ ` __tc_prefs_slot* existing = __tc_prefs_find(full);`,
153
+ ` if (existing != nullptr) { return existing; }`,
154
+ ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
155
+ ` if (!__tc_prefs.slots[i].used) { return &__tc_prefs.slots[i]; }`,
156
+ ` }`,
157
+ ` return nullptr;`,
158
+ `}`,
159
+ ``,
160
+ `// Generic typed put: stamp the type byte, stash the value, persist via`,
161
+ `// settings_save_one, and mirror into the cache. The type byte rides along`,
162
+ `// as val[0] so h_set can recover it on the next boot without a separate`,
163
+ `// name→type map.`,
164
+ `template <typename T>`,
165
+ `static inline void __tc_prefs_put(const char* full, uint8_t tag, const T& value) {`,
166
+ ` __tc_prefs_ensure_loaded(); // begin() is gone — mount lazily here`,
167
+ ` uint8_t buf[1U + sizeof(T)];`,
168
+ ` buf[0] = tag;`,
169
+ ` (void)memcpy(&buf[1], &value, sizeof(T));`,
170
+ ` (void)settings_save_one(full, buf, sizeof(buf));`,
171
+ ` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
172
+ ` if (slot != nullptr) {`,
173
+ ` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
174
+ ` slot->type = tag;`,
175
+ ` slot->len = static_cast<uint8_t>(sizeof(buf));`,
176
+ ` (void)memcpy(slot->val, buf, sizeof(buf));`,
177
+ ` slot->used = true;`,
178
+ ` }`,
179
+ `}`,
180
+ ``,
181
+ `template <typename T>`,
182
+ `static inline T __tc_prefs_get(const char* full, uint8_t tag, T def) {`,
183
+ ` __tc_prefs_ensure_loaded();`,
184
+ ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
185
+ ` if (slot == nullptr || slot->type != tag || slot->len < (1U + sizeof(T))) {`,
186
+ ` return def;`,
187
+ ` }`,
188
+ ` T out;`,
189
+ ` (void)memcpy(&out, &slot->val[1], sizeof(T));`,
190
+ ` return out;`,
191
+ `}`,
192
+ ``,
193
+ `// ── Lifecycle (session model on top of the flat key-space) ──────────────`,
194
+ `// begin records "tc/<ns>/" as the prefix every subsequent key is built under.`,
195
+ `// end clears the prefix (NOT the cache). Both are cheap on Zephyr: the real`,
196
+ ``,
197
+ `// remove: drop one key from flash and the cache. Falls back to no-op if the`,
198
+ `// slot isn't cached (settings_delete on a missing key returns -ENOENT, which`,
199
+ `// we swallow).`,
200
+ `static inline void __tc_prefs_remove(const char* full) {`,
201
+ ` __tc_prefs_ensure_loaded();`,
202
+ ` (void)settings_delete(full);`,
203
+ ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
204
+ ` if (slot != nullptr) { slot->used = false; }`,
205
+ `}`,
206
+ ``,
207
+ `// clear: drop every key under the active namespace. We walk the cache (the`,
208
+ `// authoritative list of keys we have written) and settings_delete each whose`,
209
+ `// full name starts with the active ns prefix, then mark the slot free. Only`,
210
+ `// the active namespace is cleared — keys under other namespaces survive.`,
211
+ `static inline void __tc_prefs_clear(const char* prefix) {`,
212
+ ` __tc_prefs_ensure_loaded();`,
213
+ ` const size_t plen = strlen(prefix);`,
214
+ ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
215
+ ` if (__tc_prefs.slots[i].used &&`,
216
+ ` strncmp(__tc_prefs.slots[i].key, prefix, plen) == 0) {`,
217
+ ` (void)settings_delete(__tc_prefs.slots[i].key);`,
218
+ ` __tc_prefs.slots[i].used = false;`,
219
+ ` }`,
220
+ ` }`,
221
+ `}`,
222
+ ``,
223
+ `// ── Typed accessors ─────────────────────────────────────────────────────`,
224
+ `// Each put_* stamps its tag byte and persists; each get_* reads the cache and`,
225
+ `// returns the default on miss/type-mismatch (mirrors ESP32 Preferences).`,
226
+ `static inline void __tc_prefs_put_int(const char* full, int32_t v) {`,
227
+ ` __tc_prefs_put<int32_t>(full, ${TAG.INT}, v);`,
228
+ `}`,
229
+ `static inline int32_t __tc_prefs_get_int(const char* full, int32_t def) {`,
230
+ ` return __tc_prefs_get<int32_t>(full, ${TAG.INT}, def);`,
231
+ `}`,
232
+ `static inline void __tc_prefs_put_uint(const char* full, uint32_t v) {`,
233
+ ` __tc_prefs_put<uint32_t>(full, ${TAG.UINT}, v);`,
234
+ `}`,
235
+ `static inline uint32_t __tc_prefs_get_uint(const char* full, uint32_t def) {`,
236
+ ` return __tc_prefs_get<uint32_t>(full, ${TAG.UINT}, def);`,
237
+ `}`,
238
+ `static inline void __tc_prefs_put_bool(const char* full, bool v) {`,
239
+ ` uint8_t b = v ? 1U : 0U;`,
240
+ ` __tc_prefs_put<uint8_t>(full, ${TAG.BOOL}, b);`,
241
+ `}`,
242
+ `static inline bool __tc_prefs_get_bool(const char* full, bool def) {`,
243
+ ` uint8_t b = __tc_prefs_get<uint8_t>(full, ${TAG.BOOL}, def ? 1U : 0U);`,
244
+ ` return b != 0U;`,
245
+ `}`,
246
+ `static inline void __tc_prefs_put_float(const char* full, float v) {`,
247
+ ` __tc_prefs_put<float>(full, ${TAG.FLOAT}, v);`,
248
+ `}`,
249
+ `static inline float __tc_prefs_get_float(const char* full, float def) {`,
250
+ ` return __tc_prefs_get<float>(full, ${TAG.FLOAT}, def);`,
251
+ `}`,
252
+ ``,
253
+ `// Strings are length-prefixed into the value buffer (capped at STR_LEN so the`,
254
+ `// whole slot still fits in __TC_PREFS_VAL_LEN). get copies out with a NUL and`,
255
+ `// returns def when the slot is absent or the wrong type.`,
256
+ `static inline void __tc_prefs_put_string(const char* full, const char* v) {`,
257
+ ` __tc_prefs_ensure_loaded();`,
258
+ ` uint8_t buf[1U + __TC_PREFS_STR_LEN];`,
259
+ ` buf[0] = ${TAG.STRING};`,
260
+ ` size_t n = strlen(v);`,
261
+ ` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
262
+ ` (void)memcpy(&buf[1], v, n);`,
263
+ ` (void)settings_save_one(full, buf, 1U + n);`,
264
+ ` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
265
+ ` if (slot != nullptr) {`,
266
+ ` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
267
+ ` slot->type = ${TAG.STRING};`,
268
+ ` slot->len = static_cast<uint8_t>(1U + static_cast<uint8_t>(n));`,
269
+ ` (void)memcpy(slot->val, buf, 1U + n);`,
270
+ ` slot->used = true;`,
271
+ ` }`,
272
+ `}`,
273
+ `static inline const char* __tc_prefs_get_string(const char* full, const char* def) {`,
274
+ ` __tc_prefs_ensure_loaded();`,
275
+ ` static char out[__TC_PREFS_STR_LEN + 1U];`,
276
+ ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
277
+ ` if (slot == nullptr || slot->type != ${TAG.STRING} || slot->len < 1U) {`,
278
+ ` return def;`,
279
+ ` }`,
280
+ ` size_t n = slot->len - 1U;`,
281
+ ` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
282
+ ` (void)memcpy(out, &slot->val[1], n);`,
283
+ ` out[n] = '\\0';`,
284
+ ` return out;`,
285
+ `}`,
286
+ `// CUTTLEFISH_PREFS_END`,
287
+ ``,
288
+ ];
289
+ }
290
+
291
+ /**
292
+ * Resolve a HAL preferences.* op to Zephyr C++ via the __tc_prefs_* shim.
293
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning
294
+ * ops (the get_* family). The `default` arm throws the standard unsupported-op
295
+ * error so the manifest validator's per-op probe stays honest.
296
+ */
297
+ export function lowerPreferences(op: HALOpIR): { code?: string; expression?: string } {
298
+ const o = op as any;
299
+
300
+ switch (op.operation) {
301
+ case 'preferences.clear':
302
+ return { code: `__tc_prefs_clear("tc/${unq(o.ns)}/");` };
303
+ case 'preferences.remove':
304
+ return { code: `__tc_prefs_remove("tc/${unq(o.ns)}/${unq(o.key)}");` };
305
+ case 'preferences.put_int':
306
+ return { code: `__tc_prefs_put_int("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
307
+ case 'preferences.get_int':
308
+ return { expression: `__tc_prefs_get_int("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
309
+ case 'preferences.put_bool':
310
+ return { code: `__tc_prefs_put_bool("tc/${unq(o.ns)}/${unq(o.key)}", ${o.value ? 'true' : 'false'});` };
311
+ case 'preferences.get_bool':
312
+ return { expression: `__tc_prefs_get_bool("tc/${unq(o.ns)}/${unq(o.key)}", ${o.defaultValue ? 'true' : 'false'})` };
313
+ case 'preferences.put_float':
314
+ return { code: `__tc_prefs_put_float("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
315
+ case 'preferences.get_float':
316
+ return { expression: `__tc_prefs_get_float("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
317
+ case 'preferences.put_string':
318
+ return { code: `__tc_prefs_put_string("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
319
+ case 'preferences.get_string':
320
+ return { expression: `__tc_prefs_get_string("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
321
+ default:
322
+ throw new Error(
323
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
324
+ `Open an issue or use rawCpp() to emit it manually.`,
325
+ );
326
+ }
327
+ }