@typecad/framework-zephyr 1.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/async/timer-polyfill.d.ts +10 -0
  4. package/dist/async/timer-polyfill.js +95 -0
  5. package/dist/chips/controllers.d.ts +21 -0
  6. package/dist/chips/controllers.js +66 -0
  7. package/dist/chips/esp32.d.ts +2 -0
  8. package/dist/chips/esp32.js +59 -0
  9. package/dist/chips/esp32s3.d.ts +2 -0
  10. package/dist/chips/esp32s3.js +54 -0
  11. package/dist/chips/index.d.ts +15 -0
  12. package/dist/chips/index.js +45 -0
  13. package/dist/chips/resolve.d.ts +9 -0
  14. package/dist/chips/resolve.js +121 -0
  15. package/dist/chips/types.d.ts +154 -0
  16. package/dist/chips/types.js +14 -0
  17. package/dist/chips/xiao-ble.d.ts +2 -0
  18. package/dist/chips/xiao-ble.js +61 -0
  19. package/dist/debug-codegen.d.ts +33 -0
  20. package/dist/debug-codegen.js +162 -0
  21. package/dist/display/gfx.d.ts +17 -0
  22. package/dist/display/gfx.js +171 -0
  23. package/dist/display/index.d.ts +20 -0
  24. package/dist/display/index.js +53 -0
  25. package/dist/display/profiles.d.ts +25 -0
  26. package/dist/display/profiles.js +41 -0
  27. package/dist/display/touch-adapter.d.ts +9 -0
  28. package/dist/display/touch-adapter.js +95 -0
  29. package/dist/display/ui-adapter.d.ts +21 -0
  30. package/dist/display/ui-adapter.js +534 -0
  31. package/dist/dt-config/kconfig.d.ts +27 -0
  32. package/dist/dt-config/kconfig.js +283 -0
  33. package/dist/dt-config/overlay.d.ts +35 -0
  34. package/dist/dt-config/overlay.js +246 -0
  35. package/dist/framework.manifest.d.ts +98 -0
  36. package/dist/framework.manifest.js +525 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.js +12 -0
  39. package/dist/lowering/adc.d.ts +19 -0
  40. package/dist/lowering/adc.js +76 -0
  41. package/dist/lowering/ble.d.ts +20 -0
  42. package/dist/lowering/ble.js +419 -0
  43. package/dist/lowering/board.d.ts +9 -0
  44. package/dist/lowering/board.js +33 -0
  45. package/dist/lowering/gpio.d.ts +12 -0
  46. package/dist/lowering/gpio.js +123 -0
  47. package/dist/lowering/http.d.ts +23 -0
  48. package/dist/lowering/http.js +482 -0
  49. package/dist/lowering/i2c.d.ts +15 -0
  50. package/dist/lowering/i2c.js +114 -0
  51. package/dist/lowering/index.d.ts +29 -0
  52. package/dist/lowering/index.js +93 -0
  53. package/dist/lowering/interrupts.d.ts +21 -0
  54. package/dist/lowering/interrupts.js +91 -0
  55. package/dist/lowering/mqtt.d.ts +19 -0
  56. package/dist/lowering/mqtt.js +366 -0
  57. package/dist/lowering/power.d.ts +9 -0
  58. package/dist/lowering/power.js +64 -0
  59. package/dist/lowering/preferences.d.ts +23 -0
  60. package/dist/lowering/preferences.js +345 -0
  61. package/dist/lowering/pulse.d.ts +7 -0
  62. package/dist/lowering/pulse.js +82 -0
  63. package/dist/lowering/pwm.d.ts +15 -0
  64. package/dist/lowering/pwm.js +59 -0
  65. package/dist/lowering/random.d.ts +18 -0
  66. package/dist/lowering/random.js +129 -0
  67. package/dist/lowering/spi.d.ts +17 -0
  68. package/dist/lowering/spi.js +113 -0
  69. package/dist/lowering/timing.d.ts +9 -0
  70. package/dist/lowering/timing.js +63 -0
  71. package/dist/lowering/tone.d.ts +10 -0
  72. package/dist/lowering/tone.js +50 -0
  73. package/dist/lowering/uart.d.ts +15 -0
  74. package/dist/lowering/uart.js +102 -0
  75. package/dist/lowering/util.d.ts +4 -0
  76. package/dist/lowering/util.js +12 -0
  77. package/dist/lowering/wdt.d.ts +15 -0
  78. package/dist/lowering/wdt.js +80 -0
  79. package/dist/lowering/wifi.d.ts +21 -0
  80. package/dist/lowering/wifi.js +447 -0
  81. package/dist/lowering/worker-backing.d.ts +14 -0
  82. package/dist/lowering/worker-backing.js +79 -0
  83. package/dist/lowering/worker.d.ts +6 -0
  84. package/dist/lowering/worker.js +14 -0
  85. package/dist/strategy.d.ts +168 -0
  86. package/dist/strategy.js +1094 -0
  87. package/dist/toolchain/debug-config.d.ts +82 -0
  88. package/dist/toolchain/debug-config.js +359 -0
  89. package/dist/toolchain/index.d.ts +73 -0
  90. package/dist/toolchain/index.js +427 -0
  91. package/dist/toolchain/scaffold.d.ts +15 -0
  92. package/dist/toolchain/scaffold.js +196 -0
  93. package/dist/toolchain/west-discover.d.ts +40 -0
  94. package/dist/toolchain/west-discover.js +203 -0
  95. package/dist/toolchain/west-spawn.d.ts +39 -0
  96. package/dist/toolchain/west-spawn.js +117 -0
  97. package/package.json +82 -0
  98. package/src/async/timer-polyfill.ts +107 -0
  99. package/src/chips/controllers.ts +74 -0
  100. package/src/chips/esp32.ts +62 -0
  101. package/src/chips/esp32s3.ts +57 -0
  102. package/src/chips/index.ts +54 -0
  103. package/src/chips/resolve.ts +146 -0
  104. package/src/chips/types.ts +163 -0
  105. package/src/chips/xiao-ble.ts +64 -0
  106. package/src/debug-codegen.ts +207 -0
  107. package/src/display/gfx.ts +190 -0
  108. package/src/display/index.ts +70 -0
  109. package/src/display/profiles.ts +63 -0
  110. package/src/display/touch-adapter.ts +100 -0
  111. package/src/display/ui-adapter.ts +558 -0
  112. package/src/dt-config/kconfig.ts +308 -0
  113. package/src/dt-config/overlay.ts +293 -0
  114. package/src/framework.manifest.ts +545 -0
  115. package/src/index.ts +23 -0
  116. package/src/lowering/adc.ts +102 -0
  117. package/src/lowering/ble.ts +426 -0
  118. package/src/lowering/board.ts +38 -0
  119. package/src/lowering/gpio.ts +148 -0
  120. package/src/lowering/http.ts +488 -0
  121. package/src/lowering/i2c.ts +126 -0
  122. package/src/lowering/index.ts +86 -0
  123. package/src/lowering/interrupts.ts +114 -0
  124. package/src/lowering/mqtt.ts +371 -0
  125. package/src/lowering/power.ts +70 -0
  126. package/src/lowering/preferences.ts +354 -0
  127. package/src/lowering/pulse.ts +93 -0
  128. package/src/lowering/pwm.ts +74 -0
  129. package/src/lowering/random.ts +135 -0
  130. package/src/lowering/spi.ts +124 -0
  131. package/src/lowering/timing.ts +72 -0
  132. package/src/lowering/tone.ts +60 -0
  133. package/src/lowering/uart.ts +111 -0
  134. package/src/lowering/util.ts +12 -0
  135. package/src/lowering/wdt.ts +84 -0
  136. package/src/lowering/wifi.ts +452 -0
  137. package/src/lowering/worker-backing.ts +94 -0
  138. package/src/lowering/worker.ts +17 -0
  139. package/src/strategy.ts +1265 -0
  140. package/src/toolchain/debug-config.ts +399 -0
  141. package/src/toolchain/index.ts +459 -0
  142. package/src/toolchain/scaffold.ts +196 -0
  143. package/src/toolchain/west-discover.ts +233 -0
  144. package/src/toolchain/west-spawn.ts +141 -0
@@ -0,0 +1,482 @@
1
+ // ---------------------------------------------------------------------------
2
+ // HTTP/S client lowering — Zephyr socket + http_client_req
3
+ //
4
+ // Unlike ESP-IDF's esp_http_client (which folds URL parse + DNS + connect +
5
+ // TLS into one perform call), Zephyr's <zephyr/net/http/client.h> runs
6
+ // http_client_req() over a *pre-connected* socket. The shim therefore owns the
7
+ // full connect path: parse scheme://host[:port]/path → getaddrinfo → socket
8
+ // (IPPROTO_TCP or IPPROTO_TLS_1_2 for https) → setsockopt TLS sec tags /
9
+ // hostname / peer-verify → connect() → http_client_req → accumulate body.
10
+ //
11
+ // One request slot at a time (embedded-friendly): factory/reset → setters →
12
+ // begin (inside send) → perform. Setters may run before begin and must survive
13
+ // it (req.timeout(ms); req.send()); options are cleared by __tc_http_reset
14
+ // when a new request starts (Http.get/post/…). Response body is heap-capped
15
+ // (default 8 KB), NUL-terminated, valid until the next begin/perform/reset.
16
+ //
17
+ // TLS: https selects IPPROTO_TLS_1_2 + NET_SOCKETS_SOCKOPT_TLS (which selects
18
+ // mbedTLS in Kconfig). insecure() sets TLS_PEER_VERIFY_NONE; caCert(pem)
19
+ // registers the PEM via tls_credential_add as a CA_CERTIFICATE sec tag and
20
+ // keeps verification required. No cert bundle attach (Zephyr has no ESP-IDF
21
+ // crt_bundle equivalent); https without caCert/insecure still requires the
22
+ // caller to have registered a CA, otherwise the handshake fails — that is the
23
+ // safe default.
24
+ //
25
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
26
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
27
+ // ---------------------------------------------------------------------------
28
+ /** Render a HAL field: pass through (already rendered by the resolver). */
29
+ function s(v) {
30
+ return String(v);
31
+ }
32
+ /**
33
+ * The HTTP runtime shim. All helpers are `static`/`inline` so unused ones
34
+ * don't trip -Wunused-function in the single generated TU (mirrors wifi.ts).
35
+ *
36
+ * The connect/perform path runs on the calling thread for the blocking send,
37
+ * and on the system workqueue for the async send_start/done split. Completion
38
+ * of the async path is signaled with a k_sem (give/take) which issues a full
39
+ * memory barrier, so the consumer observes the resp/status/ok writes the
40
+ * worker made before signaling — the same handoff guarantee ESP32's
41
+ * xTaskNotifyGive provides.
42
+ */
43
+ export function httpInitLines() {
44
+ return [
45
+ `// CUTTLEFISH_HTTP_BEGIN`,
46
+ `#define __TC_HTTP_DEFAULT_MAX_BODY 8192`,
47
+ `#define __TC_HTTP_MAX_HEADERS 8`,
48
+ `#define __TC_HTTP_SEC_TAG 1`,
49
+ ``,
50
+ `// Single-slot request/response state. scheme/host/port/path are parsed`,
51
+ `// from the url at perform time and drive the socket family + TLS choice.`,
52
+ `// ca_cert is user-owned PEM; insecure skips peer verification entirely.`,
53
+ `static struct {`,
54
+ ` char url[512];`,
55
+ ` char scheme[8]; // "http" or "https"`,
56
+ ` char host[128];`,
57
+ ` uint16_t port; // 80 / 443 unless overridden in the url`,
58
+ ` char path[256]; // resource path beginning with '/'`,
59
+ ` enum http_method method;`,
60
+ ` int32_t timeout_ms;`,
61
+ ` size_t max_body;`,
62
+ ` const char* body; // request body (user-owned)`,
63
+ ` int32_t body_len;`,
64
+ ` bool insecure;`,
65
+ ` const char* ca_cert; // PEM, user-owned; nullptr = no pinned CA`,
66
+ ` const char* hdr_name[__TC_HTTP_MAX_HEADERS];`,
67
+ ` const char* hdr_value[__TC_HTTP_MAX_HEADERS];`,
68
+ ` int32_t hdr_count;`,
69
+ ` // response`,
70
+ ` int32_t status; // HTTP status code (0 = none/error)`,
71
+ ` volatile bool done; // set when a perform completes (ok or fail)`,
72
+ ` bool ok; // transport ok AND 2xx`,
73
+ ` char* resp; // heap body buffer (max_body + 1, NUL-terminated)`,
74
+ ` size_t resp_len;`,
75
+ ` // Dedicated recv buffer for http_client_req. MUST be separate from resp:`,
76
+ ` // http_client_req writes the raw response (headers + body) into recv_buf`,
77
+ ` // and the parser walks it; the body callback then memcpy's body_frag into`,
78
+ ` // resp. Sharing the two means the callback clobbers the buffer the parser`,
79
+ ` // is still reading → EBADMSG (-77) on chunked/larger responses.`,
80
+ ` uint8_t recvbuf[1024];`,
81
+ ` int64_t content_length; // -1 = unknown`,
82
+ ` char resp_header[128]; // scratch for response_header()`,
83
+ ` // staged header fields for http_client_req: a NULL-terminated list of`,
84
+ ` // "Name: Value\\r\\n" strings built from hdr_name/hdr_value at perform.`,
85
+ ` const char* hdr_fields[__TC_HTTP_MAX_HEADERS + 1];`,
86
+ ` char hdr_field_buf[__TC_HTTP_MAX_HEADERS][64];`,
87
+ ` // async completion signal (memory barrier between worker and poller)`,
88
+ ` struct k_sem done_sem;`,
89
+ ` bool sem_inited;`,
90
+ ` struct k_work perform_work;`,
91
+ `} __tc_http;`,
92
+ ``,
93
+ `// ── URL parser ──────────────────────────────────────────────────────────`,
94
+ `// Splits "scheme://host[:port]/path" into the struct fields. http→80,`,
95
+ `// https→443. Missing path becomes "/". Returns false on an unparseable url.`,
96
+ `static bool __tc_http_parse_url(const char* url) {`,
97
+ ` const char* p = url;`,
98
+ ` // scheme up to "://"`,
99
+ ` const char* sep = strstr(p, "://");`,
100
+ ` if (sep == nullptr) return false;`,
101
+ ` size_t scheme_len = static_cast<size_t>(sep - p);`,
102
+ ` if (scheme_len >= sizeof(__tc_http.scheme)) scheme_len = sizeof(__tc_http.scheme) - 1U;`,
103
+ ` for (size_t i = 0U; i < scheme_len; i++) {`,
104
+ ` char c = p[i];`,
105
+ ` __tc_http.scheme[i] = (c >= 'A' && c <= 'Z') ? static_cast<char>(c + 32) : c;`,
106
+ ` }`,
107
+ ` __tc_http.scheme[scheme_len] = '\\0';`,
108
+ ` p = sep + 3; // past "://"`,
109
+ ` // host up to ':' (port), '/' (path), or end`,
110
+ ` size_t host_len = 0U;`,
111
+ ` bool is_https = (strcmp(__tc_http.scheme, "https") == 0);`,
112
+ ` __tc_http.port = is_https ? 443U : 80U;`,
113
+ ` while (*p != '\\0' && *p != ':' && *p != '/' && host_len < (sizeof(__tc_http.host) - 1U)) {`,
114
+ ` __tc_http.host[host_len++] = *p++;`,
115
+ ` }`,
116
+ ` __tc_http.host[host_len] = '\\0';`,
117
+ ` // optional :port`,
118
+ ` if (*p == ':') {`,
119
+ ` p++;`,
120
+ ` uint32_t portval = 0U;`,
121
+ ` while (*p >= '0' && *p <= '9') {`,
122
+ ` portval = (portval * 10U) + static_cast<uint32_t>(*p - '0');`,
123
+ ` p++;`,
124
+ ` if (portval > 65535U) { portval = 65535U; }`,
125
+ ` }`,
126
+ ` if (portval > 0U) __tc_http.port = static_cast<uint16_t>(portval);`,
127
+ ` }`,
128
+ ` // path (defaults to "/")`,
129
+ ` if (*p == '/') {`,
130
+ ` size_t path_len = 0U;`,
131
+ ` while (*p != '\\0' && path_len < (sizeof(__tc_http.path) - 1U)) {`,
132
+ ` __tc_http.path[path_len++] = *p++;`,
133
+ ` }`,
134
+ ` __tc_http.path[path_len] = '\\0';`,
135
+ ` } else {`,
136
+ ` __tc_http.path[0] = '/';`,
137
+ ` __tc_http.path[1] = '\\0';`,
138
+ ` }`,
139
+ ` return host_len > 0U;`,
140
+ `}`,
141
+ ``,
142
+ `// ── response callback ───────────────────────────────────────────────────`,
143
+ `// http_response_cb_t: accumulate each body fragment into resp up to max_body,`,
144
+ `// and capture the status code + content length from the first response.`,
145
+ `static int __tc_http_resp_cb(struct http_response* rsp,`,
146
+ ` enum http_final_call final_data,`,
147
+ ` void* user_data) {`,
148
+ ` (void)final_data;`,
149
+ ` (void)user_data;`,
150
+ ` if (rsp->http_status_code != 0U && __tc_http.status == 0) {`,
151
+ ` __tc_http.status = static_cast<int32_t>(rsp->http_status_code);`,
152
+ ` }`,
153
+ ` if (rsp->cl_present != 0 && __tc_http.content_length < 0) {`,
154
+ ` __tc_http.content_length = static_cast<int64_t>(rsp->content_length);`,
155
+ ` }`,
156
+ ` if (__tc_http.resp != nullptr && rsp->body_frag_start != nullptr && rsp->body_frag_len > 0U) {`,
157
+ ` size_t room = __tc_http.max_body - __tc_http.resp_len;`,
158
+ ` size_t n = (rsp->body_frag_len < room) ? rsp->body_frag_len : room;`,
159
+ ` (void)memcpy(__tc_http.resp + __tc_http.resp_len, rsp->body_frag_start, n);`,
160
+ ` __tc_http.resp_len += n;`,
161
+ ` }`,
162
+ ` return 0;`,
163
+ `}`,
164
+ ``,
165
+ `// ── socket connect ──────────────────────────────────────────────────────`,
166
+ `// zsock_getaddrinfo → zsock_socket(TCP | TLS_1_2) → TLS sockopts →`,
167
+ `// zsock_connect. Uses the zsock_* (native Zephyr socket) API directly rather`,
168
+ `// than the bare POSIX aliases: the POSIX socket() dispatcher (under`,
169
+ `// CONFIG_POSIX_API) returns ENOENT when the net backend isn't registered as`,
170
+ `// a POSIX socket family, whereas zsock_socket talks straight to the net`,
171
+ `// socket service registered by NET_SOCKETS. zsock_* calls return negative`,
172
+ `// errno on failure (not -1 + errno). For https, a pinned CA (ca_cert) is`,
173
+ `// registered as a sec tag and verification kept required; insecure()`,
174
+ `// relaxes it to NONE. Returns the connected fd or -1.`,
175
+ `static int __tc_http_open_socket(void) {`,
176
+ ` bool is_https = (strcmp(__tc_http.scheme, "https") == 0);`,
177
+ ` char port_str[8];`,
178
+ ` (void)snprintk(port_str, sizeof(port_str), "%u", static_cast<unsigned int>(__tc_http.port));`,
179
+ ` struct zsock_addrinfo hints = { 0 };`,
180
+ ` hints.ai_family = AF_INET;`,
181
+ ` hints.ai_socktype = SOCK_STREAM;`,
182
+ ` struct zsock_addrinfo* res = nullptr;`,
183
+ ` int gai = zsock_getaddrinfo(__tc_http.host, port_str, &hints, &res);`,
184
+ ` if (gai != 0 || res == nullptr) {`,
185
+ ` printk("tc-http: DNS failed for %s (gai=%d)\\n", __tc_http.host, gai);`,
186
+ ` return -1;`,
187
+ ` }`,
188
+ ` // IPPROTO_TLS_1_2 + the SOL_TLS sockopts are gated on`,
189
+ ` // CONFIG_NET_SOCKETS_SOCKOPT_TLS (which selects mbedTLS). A plain-HTTP`,
190
+ ` // build (the default — TLS isn't in the kconfig block because mbedTLS`,
191
+ ` // needs a full user-config symbol matrix to link) compiles the TLS branch`,
192
+ ` // out entirely, so it never references the unlinked tls_credential_add /`,
193
+ ` // mbedtls symbols. HTTPS targets opt in via a per-program kconfig override.`,
194
+ ` int sock = -1;`,
195
+ ` if (is_https) {`,
196
+ `#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)`,
197
+ ` int sock_tls = zsock_socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2);`,
198
+ ` if (sock_tls >= 0) {`,
199
+ ` // Register the pinned CA as the active sec tag. tls_credential_add`,
200
+ ` // is idempotent-enough for the single-slot model: a duplicate tag`,
201
+ ` // is an error we ignore, and the most recently added cert wins.`,
202
+ ` if (__tc_http.ca_cert != nullptr) {`,
203
+ ` (void)tls_credential_add(__TC_HTTP_SEC_TAG,`,
204
+ ` TLS_CREDENTIAL_CA_CERTIFICATE,`,
205
+ ` __tc_http.ca_cert, strlen(__tc_http.ca_cert) + 1U);`,
206
+ ` sec_tag_t tags[1] = { __TC_HTTP_SEC_TAG };`,
207
+ ` (void)zsock_setsockopt(sock_tls, SOL_TLS, TLS_SEC_TAG_LIST, tags, sizeof(tags));`,
208
+ ` }`,
209
+ ` // Hostname SNI + verification. insecure() disables verification;`,
210
+ ` // the default (no caCert, no insecure) leaves verification required`,
211
+ ` // with no sec tag, so the handshake fails safely until a CA is set.`,
212
+ ` (void)zsock_setsockopt(sock_tls, SOL_TLS, TLS_HOSTNAME, __tc_http.host, strlen(__tc_http.host) + 1U);`,
213
+ ` int32_t verify = __tc_http.insecure ? TLS_PEER_VERIFY_NONE : TLS_PEER_VERIFY_REQUIRED;`,
214
+ ` (void)zsock_setsockopt(sock_tls, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify));`,
215
+ ` }`,
216
+ ` sock = sock_tls;`,
217
+ `#else`,
218
+ ` printk("tc-http: https requested but CONFIG_NET_SOCKETS_SOCKOPT_TLS is off\\n");`,
219
+ ` sock = -1;`,
220
+ `#endif`,
221
+ ` } else {`,
222
+ ` sock = zsock_socket(res->ai_family, res->ai_socktype, IPPROTO_TCP);`,
223
+ ` }`,
224
+ ` if (sock < 0) {`,
225
+ ` printk("tc-http: socket() failed (errno=%d)\\n", -sock);`,
226
+ ` zsock_freeaddrinfo(res);`,
227
+ ` return -1;`,
228
+ ` }`,
229
+ ` int cret = zsock_connect(sock, res->ai_addr, res->ai_addrlen);`,
230
+ ` zsock_freeaddrinfo(res);`,
231
+ ` if (cret < 0) {`,
232
+ ` printk("tc-http: connect(%s:%u) failed (errno=%d)\\n", __tc_http.host,`,
233
+ ` static_cast<unsigned int>(__tc_http.port), -cret);`,
234
+ ` zsock_close(sock);`,
235
+ ` return -1;`,
236
+ ` }`,
237
+ ` return sock;`,
238
+ `}`,
239
+ ``,
240
+ `/** Clear request options + response; called when Http.get/post/… starts a new request. */`,
241
+ `static inline void __tc_http_reset(void) {`,
242
+ ` if (__tc_http.resp != nullptr) { delete[] __tc_http.resp; __tc_http.resp = nullptr; }`,
243
+ ` __tc_http.timeout_ms = 15000;`,
244
+ ` __tc_http.max_body = __TC_HTTP_DEFAULT_MAX_BODY;`,
245
+ ` __tc_http.body = nullptr;`,
246
+ ` __tc_http.body_len = 0;`,
247
+ ` __tc_http.insecure = false;`,
248
+ ` __tc_http.ca_cert = nullptr;`,
249
+ ` __tc_http.hdr_count = 0;`,
250
+ ` __tc_http.status = 0;`,
251
+ ` __tc_http.done = false;`,
252
+ ` __tc_http.ok = false;`,
253
+ ` __tc_http.resp_len = 0;`,
254
+ ` __tc_http.content_length = -1;`,
255
+ ` __tc_http.resp_header[0] = '\\0';`,
256
+ ` __tc_http.url[0] = '\\0';`,
257
+ `}`,
258
+ ``,
259
+ `/** Stage the request. Setters (timeout/max_body/body/tls/headers) survive begin. */`,
260
+ `static inline void __tc_http_begin(enum http_method method, const char* url) {`,
261
+ ` (void)strncpy(__tc_http.url, url, sizeof(__tc_http.url) - 1U);`,
262
+ ` __tc_http.url[sizeof(__tc_http.url) - 1U] = '\\0';`,
263
+ ` __tc_http.method = method;`,
264
+ ` // Clear only the per-request response state — keep options set before send().`,
265
+ ` if (__tc_http.resp != nullptr) { delete[] __tc_http.resp; __tc_http.resp = nullptr; }`,
266
+ ` __tc_http.status = 0;`,
267
+ ` __tc_http.done = false;`,
268
+ ` __tc_http.ok = false;`,
269
+ ` __tc_http.resp_len = 0;`,
270
+ ` __tc_http.content_length = -1;`,
271
+ ` __tc_http.resp_header[0] = '\\0';`,
272
+ `}`,
273
+ ``,
274
+ `static inline void __tc_http_set_header(const char* name, const char* value) {`,
275
+ ` if (__tc_http.hdr_count < __TC_HTTP_MAX_HEADERS) {`,
276
+ ` __tc_http.hdr_name[__tc_http.hdr_count] = name;`,
277
+ ` __tc_http.hdr_value[__tc_http.hdr_count] = value;`,
278
+ ` __tc_http.hdr_count++;`,
279
+ ` }`,
280
+ `}`,
281
+ ``,
282
+ `static inline void __tc_http_set_timeout(int32_t ms) { __tc_http.timeout_ms = ms; }`,
283
+ ``,
284
+ `static inline void __tc_http_set_max_body(size_t bytes) { __tc_http.max_body = bytes; }`,
285
+ ``,
286
+ `// json=true stages Content-Type: application/json as an extra header so the`,
287
+ `// server treats the body as JSON. The body itself is the caller's string.`,
288
+ `static inline void __tc_http_set_body(const char* data, bool json) {`,
289
+ ` __tc_http.body = data;`,
290
+ ` __tc_http.body_len = (data != nullptr) ? static_cast<int32_t>(strlen(data)) : 0;`,
291
+ ` if (json) __tc_http_set_header("Content-Type", "application/json");`,
292
+ `}`,
293
+ ``,
294
+ `static inline void __tc_http_set_insecure(void) { __tc_http.insecure = true; }`,
295
+ ``,
296
+ `static inline void __tc_http_set_ca_cert(const char* pem) { __tc_http.ca_cert = pem; }`,
297
+ ``,
298
+ `// ── perform (the shared blocking core) ──────────────────────────────────`,
299
+ `// Parse url → open socket → build http_request → http_client_req → close.`,
300
+ `// Sets status/ok/done. On a fresh resp buffer (heap, max_body+1, NUL-terminated).`,
301
+ `static void __tc_http_perform(void) {`,
302
+ ` __tc_http.ok = false;`,
303
+ ` __tc_http.done = false;`,
304
+ ` if (!__tc_http_parse_url(__tc_http.url)) {`,
305
+ ` printk("tc-http: bad url\\n");`,
306
+ ` __tc_http.done = true;`,
307
+ ` return;`,
308
+ ` }`,
309
+ ` if (__tc_http.resp != nullptr) { delete[] __tc_http.resp; __tc_http.resp = nullptr; }`,
310
+ ` __tc_http.resp_len = 0;`,
311
+ ` __tc_http.resp = new (std::nothrow) char[__tc_http.max_body + 1U];`,
312
+ ` if (__tc_http.resp == nullptr) {`,
313
+ ` printk("tc-http: out of memory for %u-byte body\\n",`,
314
+ ` static_cast<unsigned int>(__tc_http.max_body));`,
315
+ ` __tc_http.done = true;`,
316
+ ` return;`,
317
+ ` }`,
318
+ ` __tc_http.resp[0] = '\\0';`,
319
+ ` int sock = __tc_http_open_socket();`,
320
+ ` if (sock < 0) { __tc_http.done = true; return; }`,
321
+ ` // Build the NULL-terminated header-fields list from staged headers.`,
322
+ ` for (int32_t i = 0; i < __tc_http.hdr_count && i < __TC_HTTP_MAX_HEADERS; i++) {`,
323
+ ` (void)snprintk(__tc_http.hdr_field_buf[i], sizeof(__tc_http.hdr_field_buf[i]),`,
324
+ ` "%s: %s\\r\\n", __tc_http.hdr_name[i], __tc_http.hdr_value[i]);`,
325
+ ` __tc_http.hdr_fields[i] = __tc_http.hdr_field_buf[i];`,
326
+ ` }`,
327
+ ` __tc_http.hdr_fields[(__tc_http.hdr_count < __TC_HTTP_MAX_HEADERS)`,
328
+ ` ? __tc_http.hdr_count : __TC_HTTP_MAX_HEADERS] = nullptr;`,
329
+ ` struct http_request req = { 0 };`,
330
+ ` req.method = __tc_http.method;`,
331
+ ` req.url = __tc_http.path;`,
332
+ ` req.host = __tc_http.host;`,
333
+ ` req.protocol = "HTTP/1.1";`,
334
+ ` req.response = __tc_http_resp_cb;`,
335
+ ` req.recv_buf = __tc_http.recvbuf;`,
336
+ ` req.recv_buf_len = sizeof(__tc_http.recvbuf);`,
337
+ ` req.header_fields = (__tc_http.hdr_count > 0) ? __tc_http.hdr_fields : nullptr;`,
338
+ ` if (__tc_http.body != nullptr) {`,
339
+ ` req.payload = __tc_http.body;`,
340
+ ` req.payload_len = static_cast<size_t>(__tc_http.body_len);`,
341
+ ` }`,
342
+ ` int sent = http_client_req(sock, &req, __tc_http.timeout_ms, nullptr);`,
343
+ ` // The response callback (__tc_http_resp_cb) captures the status, but Zephyr's`,
344
+ ` // parser aborts on 5xx responses (on_headers_complete returns 1 for status`,
345
+ ` // >= 500, which http_parser treats as an error → http_client_req returns`,
346
+ ` // -EBADMSG and the response callback may not fire). The on_status parser`,
347
+ ` // hook still runs first and sets http_status_code, so read it back directly`,
348
+ ` // from the response struct when the callback path didn't capture it.`,
349
+ ` if (__tc_http.status == 0 && req.internal.response.http_status_code != 0U) {`,
350
+ ` __tc_http.status = static_cast<int32_t>(req.internal.response.http_status_code);`,
351
+ ` }`,
352
+ ` // Full shutdown before close so the net_context is reclaimed promptly: a`,
353
+ ` // bare zsock_close on Zephyr leaves the TCP context in a deferred-release`,
354
+ ` // state, and rapid sequential requests exhaust NET_MAX_CONTEXTS (socket/`,
355
+ ` // connect then return -EPERM). SHUT_RDWR triggers an immediate FIN exchange.`,
356
+ ` (void)zsock_shutdown(sock, SHUT_RDWR);`,
357
+ ` (void)zsock_close(sock);`,
358
+ ` // NUL-terminate the accumulated body at the cap.`,
359
+ ` size_t end = (__tc_http.resp_len < __tc_http.max_body) ? __tc_http.resp_len : __tc_http.max_body;`,
360
+ ` __tc_http.resp[end] = '\\0';`,
361
+ ` __tc_http.ok = (sent >= 0) && __tc_http.status >= 200 && __tc_http.status < 300;`,
362
+ ` __tc_http.done = true;`,
363
+ `}`,
364
+ ``,
365
+ `/** Blocking send: perform on the calling thread, return ok. */`,
366
+ `static inline bool __tc_http_send(void) {`,
367
+ ` __tc_http_perform();`,
368
+ ` return __tc_http.ok;`,
369
+ `}`,
370
+ ``,
371
+ `// ── async split (workqueue + k_sem) ──────────────────────────────────────`,
372
+ `// perform on the system workqueue; poll __tc_http_done. k_sem_give issues a`,
373
+ `// full memory barrier on completion, so the poller observes resp/status/ok`,
374
+ `// before done flips — the same handoff ESP32's xTaskNotifyGive provides.`,
375
+ `static void __tc_http_perform_work(struct k_work* w) {`,
376
+ ` (void)w;`,
377
+ ` __tc_http_perform();`,
378
+ ` k_sem_give(&__tc_http.done_sem);`,
379
+ `}`,
380
+ ``,
381
+ `static inline void __tc_http_send_start(void) {`,
382
+ ` if (!__tc_http.sem_inited) {`,
383
+ ` k_sem_init(&__tc_http.done_sem, 0, 1);`,
384
+ ` __tc_http.sem_inited = true;`,
385
+ ` }`,
386
+ ` __tc_http.done = false;`,
387
+ ` // Drain any completion signal left from a previous request (e.g. the`,
388
+ ` // prior done() saw the volatile flag before taking the sem), so the first`,
389
+ ` // poll of this request doesn't fire on a stale signal.`,
390
+ ` (void)k_sem_take(&__tc_http.done_sem, K_NO_WAIT);`,
391
+ ` k_work_init(&__tc_http.perform_work, __tc_http_perform_work);`,
392
+ ` (void)k_work_submit(&__tc_http.perform_work);`,
393
+ `}`,
394
+ ``,
395
+ `// Poll predicate: true once the worker has signaled completion. The`,
396
+ `// k_sem_take(K_NO_WAIT) is non-blocking and consumes the signal on the first`,
397
+ `// observed completion — its barrier makes resp/status/ok safe to read.`,
398
+ `static inline bool __tc_http_done(void) {`,
399
+ ` if (__tc_http.done) return true;`,
400
+ ` if (k_sem_take(&__tc_http.done_sem, K_NO_WAIT) == 0) { __tc_http.done = true; return true; }`,
401
+ ` return false;`,
402
+ `}`,
403
+ ``,
404
+ `// ── response readers ────────────────────────────────────────────────────`,
405
+ `static inline int32_t __tc_http_status(void) { return __tc_http.status; }`,
406
+ `static inline bool __tc_http_ok(void) { return __tc_http.ok; }`,
407
+ `static inline const char* __tc_http_body(void) {`,
408
+ ` return (__tc_http.resp != nullptr) ? __tc_http.resp : "";`,
409
+ `}`,
410
+ `static inline int32_t __tc_http_content_length(void) {`,
411
+ ` return static_cast<int32_t>(__tc_http.content_length);`,
412
+ `}`,
413
+ ``,
414
+ `// Response-header lookup. The Zephyr http_client_req surface does not expose`,
415
+ `// arbitrary response headers via the recv buffer; this returns the last`,
416
+ `// header captured by the response callback path. With no per-header hook`,
417
+ `// it returns "" until a richer parser setting is wired (http_cb).`,
418
+ `static inline const char* __tc_http_response_header(const char* name) {`,
419
+ ` (void)name;`,
420
+ ` __tc_http.resp_header[0] = '\\0';`,
421
+ ` return __tc_http.resp_header;`,
422
+ `}`,
423
+ `// CUTTLEFISH_HTTP_END`,
424
+ ``,
425
+ ];
426
+ }
427
+ const METHOD_MAP = {
428
+ GET: 'HTTP_GET',
429
+ POST: 'HTTP_POST',
430
+ PUT: 'HTTP_PUT',
431
+ DELETE: 'HTTP_DELETE',
432
+ HEAD: 'HTTP_HEAD',
433
+ PATCH: 'HTTP_PATCH',
434
+ };
435
+ /**
436
+ * Resolve a HAL http.* op to Zephyr C++ via the __tc_http_* shim. Returns
437
+ * `{ code }` for statement ops, `{ expression }` for value-returning ops.
438
+ * The method string on http.begin may arrive quoted ("GET"); strip quotes and
439
+ * normalize to the Zephyr enum constant (GET→HTTP_GET, etc.).
440
+ */
441
+ export function lowerHttp(op) {
442
+ const o = op;
443
+ switch (op.operation) {
444
+ case 'http.reset':
445
+ return { code: `__tc_http_reset();` };
446
+ case 'http.begin': {
447
+ const methodName = String(o.method).replace(/^["']|["']$/g, '').toUpperCase();
448
+ const method = METHOD_MAP[methodName] ?? 'HTTP_GET';
449
+ return { code: `__tc_http_begin(${method}, ${s(o.url)});` };
450
+ }
451
+ case 'http.set_header':
452
+ return { code: `__tc_http_set_header(${s(o.name)}, ${s(o.value)});` };
453
+ case 'http.set_timeout':
454
+ return { code: `__tc_http_set_timeout(${s(o.ms)});` };
455
+ case 'http.set_max_body':
456
+ return { code: `__tc_http_set_max_body(${s(o.bytes)});` };
457
+ case 'http.set_body':
458
+ return { code: `__tc_http_set_body(${s(o.data)}, ${o.json ? 'true' : 'false'});` };
459
+ case 'http.set_insecure':
460
+ return { code: `__tc_http_set_insecure();` };
461
+ case 'http.set_ca_cert':
462
+ return { code: `__tc_http_set_ca_cert(${s(o.pem)});` };
463
+ case 'http.send':
464
+ return { expression: `__tc_http_send()` };
465
+ case 'http.send_start':
466
+ return { code: `__tc_http_send_start();` };
467
+ case 'http.done':
468
+ return { expression: `__tc_http_done()` };
469
+ case 'http.status':
470
+ return { expression: `__tc_http_status()` };
471
+ case 'http.ok':
472
+ return { expression: `__tc_http_ok()` };
473
+ case 'http.body':
474
+ return { expression: `__tc_http_body()` };
475
+ case 'http.content_length':
476
+ return { expression: `__tc_http_content_length()` };
477
+ case 'http.response_header':
478
+ return { expression: `__tc_http_response_header(${s(o.name)})` };
479
+ default:
480
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`.`);
481
+ }
482
+ }
@@ -0,0 +1,15 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the per-controller I2C state. Called from shimLines for each
5
+ * controller the program uses (gated on ctx.analysis.usesI2C).
6
+ */
7
+ export declare function i2cInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[];
8
+ /**
9
+ * Resolve a HAL i2c.* op to Zephyr C++.
10
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
11
+ */
12
+ export declare function lowerI2c(op: HALOpIR, chip: ZephyrChipDescriptor): {
13
+ code?: string;
14
+ expression?: string;
15
+ };
@@ -0,0 +1,114 @@
1
+ // ---------------------------------------------------------------------------
2
+ // I2C lowering — transactional txbuf accumulator + per-call i2c_write
3
+ //
4
+ // Mirrors the ESP32 stateful model but simpler: Zephyr's `i2c_write(bus, buf,
5
+ // len, addr)` takes the address per-call, so there's no device-handle cache.
6
+ // The begin_transmission → write* → end_transmission sequence accumulates into
7
+ // `__tc_i2c<N>_txbuf` and flushes as one `i2c_write` at end_transmission.
8
+ // Reads go through request_from → i2c_read into `__tc_i2c<N>_rxbuf`, consumed
9
+ // by subsequent i2c.read / i2c.available calls.
10
+ //
11
+ // The device handle resolves at compile time via DEVICE_DT_GET(DT_NODELABEL(...)).
12
+ // ---------------------------------------------------------------------------
13
+ import { parseControllerIndex } from './util.js';
14
+ const TXBUF_SIZE = 32;
15
+ const RXBUF_SIZE = 32;
16
+ /** The C variable prefix for a controller's state. */
17
+ function prefix(idx) {
18
+ return `__tc_i2c${idx}`;
19
+ }
20
+ /**
21
+ * Emit the per-controller I2C state. Called from shimLines for each
22
+ * controller the program uses (gated on ctx.analysis.usesI2C).
23
+ */
24
+ export function i2cInitLines(chip, controllerIndex) {
25
+ const ctrl = chip.i2c?.controllers[controllerIndex];
26
+ if (!ctrl)
27
+ return [];
28
+ const p = prefix(controllerIndex);
29
+ return [
30
+ '// CUTTLEFISH_I2C_BEGIN',
31
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
32
+ `static uint16_t ${p}_addr = 0xFFFF;`,
33
+ `static uint8_t ${p}_txbuf[${TXBUF_SIZE}];`,
34
+ `static size_t ${p}_txlen = 0;`,
35
+ `static uint8_t ${p}_rxbuf[${RXBUF_SIZE}];`,
36
+ `static size_t ${p}_rxlen = 0;`,
37
+ `static size_t ${p}_rxpos = 0;`,
38
+ '// CUTTLEFISH_I2C_END',
39
+ ];
40
+ }
41
+ /** Map a clock speed in Hz to an I2C_SPEED_* define. */
42
+ function speedForHz(hz) {
43
+ if (hz <= 100_000)
44
+ return 'I2C_SPEED_STANDARD';
45
+ if (hz <= 400_000)
46
+ return 'I2C_SPEED_FAST';
47
+ if (hz <= 1_000_000)
48
+ return 'I2C_SPEED_FAST_PLUS';
49
+ return 'I2C_SPEED_HIGH';
50
+ }
51
+ /**
52
+ * Resolve a HAL i2c.* op to Zephyr C++.
53
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
54
+ */
55
+ export function lowerI2c(op, chip) {
56
+ const o = op;
57
+ const idx = parseControllerIndex(o.bus);
58
+ const p = prefix(idx);
59
+ switch (op.operation) {
60
+ case 'i2c.begin':
61
+ // Zephyr resolves the device at compile time; begin is a no-op (device
62
+ // ready check is folded into the driver calls).
63
+ return { code: `(void)${p}_dev;` };
64
+ case 'i2c.end':
65
+ return { code: `(void)${p}_dev;` };
66
+ case 'i2c.set_clock': {
67
+ const hz = typeof o.hz === 'number' ? o.hz : parseInt(String(o.hz), 10);
68
+ const speed = isNaN(hz) ? 'I2C_SPEED_STANDARD' : speedForHz(hz || 100000);
69
+ return { code: `i2c_configure(${p}_dev, I2C_SPEED_SET(${speed}));` };
70
+ }
71
+ case 'i2c.begin_transmission':
72
+ // Record the target address + reset the pending write buffer.
73
+ return { code: `${p}_addr = static_cast<uint16_t>(${o.address}); ${p}_txlen = 0;` };
74
+ case 'i2c.write':
75
+ // Append one byte (clamped to capacity).
76
+ return { code: `if (${p}_txlen < ${TXBUF_SIZE}) { ${p}_txbuf[${p}_txlen++] = static_cast<uint8_t>(${o.data}); }` };
77
+ case 'i2c.write_bytes': {
78
+ const bytes = o.bytes ?? [];
79
+ const stmts = bytes.map((b) => `if (${p}_txlen < ${TXBUF_SIZE}) { ${p}_txbuf[${p}_txlen++] = static_cast<uint8_t>(${b}); }`);
80
+ return { code: stmts.join(' ') };
81
+ }
82
+ case 'i2c.write_buffer': {
83
+ // Copy from a user buffer (its name is o.data), clamped to capacity.
84
+ return {
85
+ code: `for (size_t __i = 0; __i < sizeof(${o.data}) && ${p}_txlen < ${TXBUF_SIZE}; __i++) { ${p}_txbuf[${p}_txlen++] = reinterpret_cast<const uint8_t*>(${o.data})[__i]; }`,
86
+ };
87
+ }
88
+ case 'i2c.end_transmission':
89
+ // Flush the accumulated txbuf to the cached address.
90
+ return { code: `i2c_write(${p}_dev, ${p}_txbuf, ${p}_txlen, ${p}_addr);` };
91
+ case 'i2c.request_from': {
92
+ const qty = o.quantity;
93
+ return {
94
+ code: `i2c_read(${p}_dev, ${p}_rxbuf, static_cast<uint32_t>(${qty}), static_cast<uint16_t>(${o.address})); ${p}_rxlen = ${qty}; ${p}_rxpos = 0;`,
95
+ };
96
+ }
97
+ case 'i2c.available':
98
+ return { expression: `(${p}_rxlen - ${p}_rxpos)` };
99
+ case 'i2c.read':
100
+ return { expression: `(${p}_rxpos < ${p}_rxlen ? ${p}_rxbuf[${p}_rxpos++] : -1)` };
101
+ case 'i2c.read_buffer': {
102
+ // Read straight into the user's buffer (o.buffer), count bytes.
103
+ const count = o.count;
104
+ return {
105
+ code: `i2c_read(${p}_dev, reinterpret_cast<uint8_t*>(${o.buffer}), static_cast<uint32_t>(${count}), ${p}_addr);`,
106
+ };
107
+ }
108
+ case 'i2c.recover':
109
+ return { code: `i2c_recover_bus(${p}_dev);` };
110
+ default:
111
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
112
+ `Open an issue or use rawCpp() to emit it manually.`);
113
+ }
114
+ }
@@ -0,0 +1,29 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import { lowerGpio } from './gpio.js';
3
+ import { lowerTiming } from './timing.js';
4
+ import { lowerAdc } from './adc.js';
5
+ import { lowerPwm } from './pwm.js';
6
+ import { lowerI2c } from './i2c.js';
7
+ import { lowerSpi } from './spi.js';
8
+ import { lowerUart } from './uart.js';
9
+ import { lowerInterrupt } from './interrupts.js';
10
+ import { lowerWdt } from './wdt.js';
11
+ import { lowerPower } from './power.js';
12
+ import { lowerTone } from './tone.js';
13
+ import { lowerPulseOrShift } from './pulse.js';
14
+ import { lowerBle } from './ble.js';
15
+ import { lowerWifi } from './wifi.js';
16
+ import { lowerHttp } from './http.js';
17
+ import { lowerMqtt } from './mqtt.js';
18
+ import { lowerPreferences } from './preferences.js';
19
+ import { lowerBoard } from './board.js';
20
+ import { lowerRandom } from './random.js';
21
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, };
22
+ /**
23
+ * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
24
+ * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
25
+ */
26
+ export declare function lowerHalOp(op: HALOpIR): {
27
+ code?: string;
28
+ expression?: string;
29
+ } | undefined;