@typecad/cuttlefish 1.0.0-alpha.6 → 1.0.0-alpha.7

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 (90) hide show
  1. package/dist/api/shared/display-adapter.d.ts +2 -1
  2. package/dist/api/shared/display-adapter.js +8 -1
  3. package/dist/api/shared/display-adapters/sdl.js +9 -2
  4. package/dist/api/shared/display-profile.d.ts +20 -3
  5. package/dist/api/shared/display-profile.js +21 -6
  6. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  7. package/dist/api/shared/framework-manifest-registry.js +25 -0
  8. package/dist/api/shared/framework-manifest.d.ts +462 -0
  9. package/dist/api/shared/framework-manifest.js +149 -0
  10. package/dist/api/shared/glcdfont.d.ts +12 -0
  11. package/dist/api/shared/glcdfont.js +124 -0
  12. package/dist/api/shared/graphics-strategy.d.ts +28 -0
  13. package/dist/api/shared/hal-op-ir.d.ts +418 -1
  14. package/dist/api/shared/hal-op-ir.js +95 -1
  15. package/dist/api/shared/index.d.ts +11 -1
  16. package/dist/api/shared/index.js +14 -0
  17. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  18. package/dist/api/shared/native-display-op-resolver.js +64 -0
  19. package/dist/api/shared/platform-strategy.d.ts +9 -0
  20. package/dist/api/shared/promise-runtime.js +78 -0
  21. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  22. package/dist/api/shared/validate-framework-manifest.js +417 -0
  23. package/dist/cli.js +109 -4
  24. package/dist/config-loader.js +20 -1
  25. package/dist/config-schema.d.ts +36 -36
  26. package/dist/create/board-spec.d.ts +4 -4
  27. package/dist/create/init-scaffold.d.ts +3 -0
  28. package/dist/create/init-scaffold.js +48 -0
  29. package/dist/create/init-templates.d.ts +2 -0
  30. package/dist/create/init-templates.js +28 -8
  31. package/dist/create/init-wizard.js +20 -1
  32. package/dist/emit/cpp-emitter.js +4 -3
  33. package/dist/emit/emitters/emitter-context.d.ts +5 -0
  34. package/dist/emit/emitters/function-emitter-impl.js +69 -59
  35. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  36. package/dist/emit/emitters/output-finalizer.js +21 -11
  37. package/dist/emit/emitters/setup.js +40 -0
  38. package/dist/emit/emitters/top-level-prep.js +2 -0
  39. package/dist/emit/emitters/ui-emitter.js +23 -8
  40. package/dist/emit/expression-renderer.js +10 -1
  41. package/dist/emit/snprintf-helpers.js +8 -0
  42. package/dist/emit/statement-renderer.js +13 -0
  43. package/dist/emit/utils/async-state-machine.js +185 -116
  44. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  45. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  46. package/dist/ir/build-ir.js +5 -1
  47. package/dist/ir/expression-to-ir.js +14 -0
  48. package/dist/ir/feature-registry.js +7 -25
  49. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  50. package/dist/ir/hal/hal-emitter.js +40 -12
  51. package/dist/ir/hal/hal-parser.d.ts +6 -0
  52. package/dist/ir/hal/hal-parser.js +74 -0
  53. package/dist/ir/hal/hal-plugins.js +561 -0
  54. package/dist/ir/identifier-collector.js +18 -0
  55. package/dist/ir/network-validation.d.ts +4 -0
  56. package/dist/ir/network-validation.js +184 -0
  57. package/dist/ir/ownership-analysis.js +14 -1
  58. package/dist/ir/program-analysis.d.ts +23 -0
  59. package/dist/ir/program-analysis.js +81 -2
  60. package/dist/ir/timing-validation.d.ts +6 -1
  61. package/dist/ir/timing-validation.js +50 -12
  62. package/dist/ir/transformers/expressions.js +58 -0
  63. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  64. package/dist/ir/transformers/variables.js +41 -3
  65. package/dist/ir/validation-orchestrator.js +3 -1
  66. package/dist/libdef/c-to-decl.d.ts +27 -0
  67. package/dist/libdef/c-to-decl.js +397 -0
  68. package/dist/libdef/component-decls.d.ts +2 -0
  69. package/dist/libdef/component-decls.js +6 -0
  70. package/dist/libdef/component-discovery.d.ts +43 -0
  71. package/dist/libdef/component-discovery.js +83 -0
  72. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  73. package/dist/libdef/cpp-to-decl.js +72 -0
  74. package/dist/libdef/idf-discovery.d.ts +7 -0
  75. package/dist/libdef/idf-discovery.js +59 -0
  76. package/dist/libdef/registry.js +5 -2
  77. package/dist/lint-cache.d.ts +59 -0
  78. package/dist/lint-cache.js +257 -0
  79. package/dist/orchestrator/graph-builder.js +6 -2
  80. package/dist/stores/display-profile-store.d.ts +1 -0
  81. package/dist/stores/display-profile-store.js +1 -0
  82. package/dist/testing.d.ts +3 -1
  83. package/dist/testing.js +3 -1
  84. package/dist/transpile.d.ts +3 -0
  85. package/dist/transpile.js +72 -27
  86. package/dist/ui-hook.d.ts +17 -1
  87. package/dist/utils/cli.js +27 -1
  88. package/dist/utils/fs.d.ts +13 -0
  89. package/dist/utils/fs.js +50 -0
  90. package/package.json +8 -4
@@ -210,6 +210,30 @@ export function loadHALModules(force = false) {
210
210
  export function getCtorIncludes(className) {
211
211
  return halCtorIncludes.get(className) ?? [];
212
212
  }
213
+ /** Map an HttpClass factory method name to its HTTP verb, or null. */
214
+ export function httpFactoryVerb(methodName) {
215
+ switch (methodName) {
216
+ case "get": return "GET";
217
+ case "post": return "POST";
218
+ case "put": return "PUT";
219
+ case "del": return "DELETE";
220
+ case "head": return "HEAD";
221
+ case "patch": return "PATCH";
222
+ default: return null;
223
+ }
224
+ }
225
+ /** Render an Http factory URL argument as C++ expression text (string
226
+ * literals quoted, identifiers/member accesses verbatim), or null when the
227
+ * argument shape can't be resolved at compile time. */
228
+ export function httpUrlArgText(arg) {
229
+ if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
230
+ return JSON.stringify(arg.text);
231
+ if (ts.isIdentifier(arg))
232
+ return arg.text;
233
+ if (ts.isPropertyAccessExpression(arg))
234
+ return arg.getText();
235
+ return null;
236
+ }
213
237
  /** Resolve a call receiver to a tracked HAL instance. */
214
238
  export function resolveHALReceiver(receiver) {
215
239
  const result = (() => {
@@ -336,6 +360,51 @@ export function resolveHALReceiver(receiver) {
336
360
  return { className: "ToneChain", fieldValues };
337
361
  }
338
362
  }
363
+ // Specialized handling for Http factory chaining
364
+ // (Http.get(url).header(...).send()): the factory records the HTTP
365
+ // verb and URL into the HttpRequest instance so send() can resolve
366
+ // this._method / this._url.
367
+ if (innerInstance.className === "HttpClass") {
368
+ const verb = httpFactoryVerb(methodName);
369
+ if (verb && receiver.arguments.length > 0) {
370
+ const urlText = httpUrlArgText(receiver.arguments[0]);
371
+ if (urlText) {
372
+ return {
373
+ className: "HttpRequest",
374
+ fieldValues: new Map([["_method", verb], ["_url", urlText]]),
375
+ };
376
+ }
377
+ }
378
+ }
379
+ // Specialized handling for BLE factory chaining
380
+ // (Ble.server(name).characteristic(uuid,type,perms).onRead(handler)):
381
+ // server() creates a BleServer with _name, _charCount from a global
382
+ // counter (persists across separate server() calls so multi-char
383
+ // servers get unique indices), _svcCount=1.
384
+ // characteristic() reads _charCount for the add_char call,
385
+ // then sets _lastChar=_charCount and increments _charCount so the next
386
+ // characteristic gets the next slot. onRead/onWrite read _lastChar.
387
+ if (innerInstance.className === "BleClass" && methodName === "server" && receiver.arguments.length > 0) {
388
+ const nameText = httpUrlArgText(receiver.arguments[0]);
389
+ if (nameText) {
390
+ return {
391
+ className: "BleServer",
392
+ fieldValues: new Map([["_name", nameText], ["_charCount", "0"], ["_lastChar", "0"], ["_svcCount", "1"]]),
393
+ };
394
+ }
395
+ }
396
+ if (innerInstance.className === "BleServer" && methodName === "characteristic") {
397
+ // characteristic(uuid, type, perms) returns this (BleServer).
398
+ // The C++ shim auto-assigns the char index at runtime via
399
+ // __tc_ble.current_char, so the resolver doesn't need to track indices.
400
+ return innerInstance;
401
+ }
402
+ if (innerInstance.className === "BleServer" && methodName === "service") {
403
+ // service() returns this (BleServer); advance the service counter.
404
+ const svc = Number(innerInstance.fieldValues.get("_svcCount") ?? "1") + 1;
405
+ innerInstance.fieldValues.set("_svcCount", String(svc));
406
+ return innerInstance;
407
+ }
339
408
  // General fallback: if the method is known to return another HAL class, carry over fields
340
409
  const classEntry = halClassRegistry.get(innerInstance.className);
341
410
  const methodEntry = classEntry?.methods.get(methodName);
@@ -345,6 +414,11 @@ export function resolveHALReceiver(receiver) {
345
414
  return { className: returnClassName, fieldValues: new Map(innerInstance.fieldValues) };
346
415
  }
347
416
  }
417
+ // Fluent `this`-returning methods (e.g. HttpRequest.header/timeout):
418
+ // the chain result is the same instance, so keep resolving through it.
419
+ if (methodEntry && methodEntry.methodNode.type && methodEntry.methodNode.type.kind === ts.SyntaxKind.ThisType) {
420
+ return innerInstance;
421
+ }
348
422
  }
349
423
  }
350
424
  // Inline new expression: new Pin(x).method()
@@ -105,6 +105,44 @@ function resolveNumericOrExpression(args, idx, instance, paramNames, callArgText
105
105
  return num;
106
106
  return resolveSemanticArg(args, idx, instance, paramNames, callArgTexts, paramDefaults);
107
107
  }
108
+ /** Normalize an optional resolved arg: an omitted optional parameter with no
109
+ * default resolves to the literal text "undefined" (see resolveExpressionText);
110
+ * map that to null so op builders can omit the field entirely. */
111
+ function dropUndefined(value) {
112
+ return value === "undefined" ? null : value;
113
+ }
114
+ /** Quote a resolved string value unless it is already a quoted literal or a
115
+ * plain identifier / member-access expression (i.e. a runtime variable). */
116
+ function quoteNonIdentifier(value) {
117
+ const t = value.trim();
118
+ if (/^".*"$/.test(t))
119
+ return t;
120
+ if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(t))
121
+ return t;
122
+ return JSON.stringify(t);
123
+ }
124
+ /** Resolve a BlePerm expression to its numeric bitmask.
125
+ * Handles: plain numbers (3), "BlePerm.Read", "BlePerm.Read | BlePerm.Notify". */
126
+ const BLE_PERM_VALUES = {
127
+ Read: 1, Write: 2, Notify: 4,
128
+ };
129
+ function resolveBlePermExpr(raw) {
130
+ if (raw === null)
131
+ return 0;
132
+ const s = String(raw).trim();
133
+ if (/^\d+$/.test(s))
134
+ return Number(s);
135
+ // Extract all BlePerm.X member names and sum their values.
136
+ let sum = 0;
137
+ const re = /BlePerm\.(\w+)/g;
138
+ let m;
139
+ let matched = false;
140
+ while ((m = re.exec(s)) !== null) {
141
+ matched = true;
142
+ sum += BLE_PERM_VALUES[m[1]] ?? 0;
143
+ }
144
+ return matched ? sum : 0;
145
+ }
108
146
  /** Extract the MCU port name from the current HAL instance, if available. */
109
147
  export function portFromInstance(instance) {
110
148
  const port = instance.fieldValues.get('_port');
@@ -312,6 +350,101 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
312
350
  return null;
313
351
  return { operation: "pwm.write", port, pin, duty };
314
352
  }
353
+ // ── RMT ──
354
+ // rmtTxInit/rmtRxInit are POSITIONAL semantic primitives (the ergonomic
355
+ // opts-object form lives in hal/rmt.ts's RmtChannel class, which
356
+ // destructures opts and forwards positionally). The resolver dispatches on
357
+ // the literal callee name, so rmt.ts must call these by their real names
358
+ // (no `as _rmtTxInit` aliasing — that breaks the switch).
359
+ // rmtTxInit(pin, resolutionHz, bit0Hi, bit0Lo, bit1Hi, bit1Lo, msbFirst, queueDepth)
360
+ case "rmtTxInit": {
361
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
362
+ const resolutionHz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
363
+ const bit0Hi = resolveNumericOrExpression(args, 2, instance, paramNames, callArgTexts, paramDefaults);
364
+ const bit0Lo = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
365
+ const bit1Hi = resolveNumericOrExpression(args, 4, instance, paramNames, callArgTexts, paramDefaults);
366
+ const bit1Lo = resolveNumericOrExpression(args, 5, instance, paramNames, callArgTexts, paramDefaults);
367
+ const msbFirst = resolveSemanticArg(args, 6, instance, paramNames, callArgTexts, paramDefaults);
368
+ const queueDepth = resolveNumericOrExpression(args, 7, instance, paramNames, callArgTexts, paramDefaults);
369
+ if (pin === null || resolutionHz === null)
370
+ return null;
371
+ const op = { operation: "rmt.tx_init", port, pin, resolutionHz, bit0Hi, bit0Lo, bit1Hi, bit1Lo };
372
+ if (queueDepth !== null)
373
+ op.queueDepth = queueDepth;
374
+ if (msbFirst !== null)
375
+ op.msbFirst = msbFirst;
376
+ return op;
377
+ }
378
+ case "rmtTxWriteBytes": {
379
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
380
+ // bytes is an array literal — resolveSemanticArg renders it positionally as "1, 2, 3".
381
+ const bytes = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
382
+ if (pin === null || bytes === null)
383
+ return null;
384
+ return { operation: "rmt.tx_write_bytes", port, pin, bytes };
385
+ }
386
+ case "rmtTxWriteSymbols": {
387
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
388
+ const symbols = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
389
+ if (pin === null || symbols === null)
390
+ return null;
391
+ return { operation: "rmt.tx_write_symbols", port, pin, symbols };
392
+ }
393
+ case "rmtTxWaitDone": {
394
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
395
+ const timeoutMs = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
396
+ if (pin === null)
397
+ return null;
398
+ const op = { operation: "rmt.tx_wait_done", port, pin };
399
+ if (timeoutMs !== null)
400
+ op.timeoutMs = timeoutMs;
401
+ return op;
402
+ }
403
+ case "rmtTxDeinit": {
404
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
405
+ if (pin === null)
406
+ return null;
407
+ return { operation: "rmt.tx_deinit", port, pin };
408
+ }
409
+ case "rmtRxInit": {
410
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
411
+ const resolutionHz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
412
+ if (pin === null || resolutionHz === null)
413
+ return null;
414
+ return { operation: "rmt.rx_init", port, pin, resolutionHz };
415
+ }
416
+ case "rmtRxOnReceived": {
417
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
418
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
419
+ if (pin === null || handler === null)
420
+ return null;
421
+ return { operation: "rmt.rx_on_received", port, pin, handler };
422
+ }
423
+ case "rmtRxStart": {
424
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
425
+ if (pin === null)
426
+ return null;
427
+ return { operation: "rmt.rx_start", port, pin };
428
+ }
429
+ case "rmtRxStop": {
430
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
431
+ if (pin === null)
432
+ return null;
433
+ return { operation: "rmt.rx_stop", port, pin };
434
+ }
435
+ case "rmtRxRead": {
436
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
437
+ const maxCount = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
438
+ if (pin === null || maxCount === null)
439
+ return null;
440
+ return { operation: "rmt.rx_read", port, pin, maxCount };
441
+ }
442
+ case "rmtRxDeinit": {
443
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
444
+ if (pin === null)
445
+ return null;
446
+ return { operation: "rmt.rx_deinit", port, pin };
447
+ }
315
448
  // ── ADC ──
316
449
  case "adcRead": {
317
450
  const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
@@ -383,6 +516,434 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
383
516
  return null;
384
517
  return { operation: "power.set_cpu_frequency", mhz };
385
518
  }
519
+ case "powerDeepSleepPin": {
520
+ const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
521
+ const level = resolveNumericArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
522
+ if (pin === null || level === null)
523
+ return null;
524
+ return { operation: "power.deep_sleep_pin", pin, level };
525
+ }
526
+ // ── WiFi ──
527
+ // Optional string args (password, dns) resolve to the text "undefined"
528
+ // when omitted at the call site (no default in the HAL signature). Treat
529
+ // that as absent so the backend emits its own default ("").
530
+ case "wifiConnect": {
531
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
532
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
533
+ const timeoutMs = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
534
+ if (ssid === null)
535
+ return null;
536
+ return {
537
+ operation: "wifi.connect",
538
+ ssid,
539
+ ...(password !== null ? { password } : {}),
540
+ timeoutMs,
541
+ blocking: true,
542
+ };
543
+ }
544
+ case "wifiConnectStart": {
545
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
546
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
547
+ if (ssid === null)
548
+ return null;
549
+ return { operation: "wifi.connect_start", ssid, ...(password !== null ? { password } : {}) };
550
+ }
551
+ case "wifiDisconnect":
552
+ return { operation: "wifi.disconnect" };
553
+ case "wifiStatus":
554
+ return { operation: "wifi.status" };
555
+ case "wifiIsConnected":
556
+ return { operation: "wifi.is_connected" };
557
+ case "wifiLocalIp":
558
+ return { operation: "wifi.local_ip" };
559
+ case "wifiRssi":
560
+ return { operation: "wifi.rssi" };
561
+ case "wifiMac":
562
+ return { operation: "wifi.mac" };
563
+ case "wifiSetHostname": {
564
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
565
+ if (name === null)
566
+ return null;
567
+ return { operation: "wifi.set_hostname", name };
568
+ }
569
+ case "wifiSetStaticIp": {
570
+ const ip = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
571
+ const gateway = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
572
+ const subnet = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
573
+ const dns = dropUndefined(resolveSemanticArg(args, 3, instance, paramNames, callArgTexts, paramDefaults));
574
+ if (ip === null || gateway === null || subnet === null)
575
+ return null;
576
+ return { operation: "wifi.set_static_ip", ip, gateway, subnet, ...(dns !== null ? { dns } : {}) };
577
+ }
578
+ case "wifiSetAutoReconnect": {
579
+ const enabled = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
580
+ if (enabled === null)
581
+ return null;
582
+ return { operation: "wifi.set_auto_reconnect", enabled: enabled === "true" };
583
+ }
584
+ case "wifiSetPowerSave": {
585
+ const mode = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
586
+ if (mode === null)
587
+ return null;
588
+ return { operation: "wifi.set_power_save", mode };
589
+ }
590
+ case "wifiSetTxPower": {
591
+ const dbm = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
592
+ if (dbm === null)
593
+ return null;
594
+ return { operation: "wifi.set_tx_power", dbm };
595
+ }
596
+ case "wifiOnEvent": {
597
+ const event = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
598
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
599
+ if (event === null || handler === null)
600
+ return null;
601
+ return { operation: "wifi.on_event", event: String(event).replace(/^["']|["']$/g, ""), handler };
602
+ }
603
+ case "wifiApStart": {
604
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
605
+ const password = dropUndefined(resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults));
606
+ const channel = dropUndefined(resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults));
607
+ const hidden = dropUndefined(resolveSemanticArg(args, 3, instance, paramNames, callArgTexts, paramDefaults));
608
+ const maxClients = dropUndefined(resolveSemanticArg(args, 4, instance, paramNames, callArgTexts, paramDefaults));
609
+ if (ssid === null)
610
+ return null;
611
+ return {
612
+ operation: "wifi.ap_start",
613
+ ssid,
614
+ ...(password !== null ? { password } : {}),
615
+ ...(channel !== null ? { channel } : {}),
616
+ ...(hidden !== null ? { hidden } : {}),
617
+ ...(maxClients !== null ? { maxClients } : {}),
618
+ };
619
+ }
620
+ case "wifiApStop":
621
+ return { operation: "wifi.ap_stop" };
622
+ case "wifiApClientCount":
623
+ return { operation: "wifi.ap_client_count" };
624
+ case "wifiApIp":
625
+ return { operation: "wifi.ap_ip" };
626
+ case "wifiApSetChannel": {
627
+ const channel = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
628
+ if (channel === null)
629
+ return null;
630
+ return { operation: "wifi.ap_set_channel", channel };
631
+ }
632
+ case "wifiApSetHidden": {
633
+ const hidden = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
634
+ if (hidden === null)
635
+ return null;
636
+ return { operation: "wifi.ap_set_hidden", hidden };
637
+ }
638
+ case "wifiApSetMaxClients": {
639
+ const maxClients = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
640
+ if (maxClients === null)
641
+ return null;
642
+ return { operation: "wifi.ap_set_max_clients", maxClients };
643
+ }
644
+ case "wifiScan":
645
+ return { operation: "wifi.scan" };
646
+ case "wifiScanStart":
647
+ return { operation: "wifi.scan_start" };
648
+ case "wifiScanCount":
649
+ return { operation: "wifi.scan_count" };
650
+ case "wifiScanSsid": {
651
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
652
+ if (index === null)
653
+ return null;
654
+ return { operation: "wifi.scan_ssid", index };
655
+ }
656
+ case "wifiScanRssi": {
657
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
658
+ if (index === null)
659
+ return null;
660
+ return { operation: "wifi.scan_rssi", index };
661
+ }
662
+ case "wifiScanEncryption": {
663
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
664
+ if (index === null)
665
+ return null;
666
+ return { operation: "wifi.scan_encryption", index };
667
+ }
668
+ case "wifiScanChannel": {
669
+ const index = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
670
+ if (index === null)
671
+ return null;
672
+ return { operation: "wifi.scan_channel", index };
673
+ }
674
+ case "wifiSaveCredentials": {
675
+ const ssid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
676
+ const password = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
677
+ if (ssid === null || password === null)
678
+ return null;
679
+ return { operation: "wifi.save_credentials", ssid, password };
680
+ }
681
+ case "wifiConnectSaved": {
682
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
683
+ return { operation: "wifi.connect_saved", timeoutMs };
684
+ }
685
+ case "wifiClearCredentials":
686
+ return { operation: "wifi.clear_credentials" };
687
+ case "wifiWaitConnected": {
688
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 15000;
689
+ return { operation: "wifi.wait_connected", timeoutMs };
690
+ }
691
+ case "wifiWaitDisconnected":
692
+ return { operation: "wifi.wait_disconnected" };
693
+ // ── HTTP ──
694
+ case "httpBegin": {
695
+ const method = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
696
+ const url = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
697
+ if (method === null || url === null)
698
+ return null;
699
+ // this._url resolved through `new HttpRequest(...)` ctor fields stores
700
+ // string literals without quotes; re-quote anything that isn't already
701
+ // a quoted literal or a plain identifier/member expression so the
702
+ // lowering emits valid C++.
703
+ return { operation: "http.begin", method, url: quoteNonIdentifier(url) };
704
+ }
705
+ case "httpReset":
706
+ return { operation: "http.reset" };
707
+ case "httpSetHeader": {
708
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
709
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
710
+ if (name === null || value === null)
711
+ return null;
712
+ return { operation: "http.set_header", name, value };
713
+ }
714
+ case "httpSetTimeout": {
715
+ const ms = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
716
+ if (ms === null)
717
+ return null;
718
+ return { operation: "http.set_timeout", ms };
719
+ }
720
+ case "httpSetMaxBody": {
721
+ const bytes = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
722
+ if (bytes === null)
723
+ return null;
724
+ return { operation: "http.set_max_body", bytes };
725
+ }
726
+ case "httpSetBody": {
727
+ const data = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
728
+ const json = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
729
+ if (data === null)
730
+ return null;
731
+ return { operation: "http.set_body", data, json: json === "true" };
732
+ }
733
+ case "httpSetInsecure":
734
+ return { operation: "http.set_insecure" };
735
+ case "httpSetCaCert": {
736
+ const pem = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
737
+ if (pem === null)
738
+ return null;
739
+ return { operation: "http.set_ca_cert", pem };
740
+ }
741
+ case "httpSend":
742
+ return { operation: "http.send", blocking: true };
743
+ case "httpSendStart":
744
+ return { operation: "http.send_start" };
745
+ case "httpStatus":
746
+ return { operation: "http.status" };
747
+ case "httpOk":
748
+ return { operation: "http.ok" };
749
+ case "httpBody":
750
+ return { operation: "http.body" };
751
+ case "httpContentLength":
752
+ return { operation: "http.content_length" };
753
+ case "httpResponseHeader": {
754
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
755
+ if (name === null)
756
+ return null;
757
+ return { operation: "http.response_header", name };
758
+ }
759
+ // ── BLE (NimBLE GATT peripheral) ──
760
+ case "bleServerBegin": {
761
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
762
+ if (name === null)
763
+ return null;
764
+ return { operation: "ble.server_begin", name: quoteNonIdentifier(name) };
765
+ }
766
+ case "bleAdvertiseStart":
767
+ return { operation: "ble.advertise_start" };
768
+ case "bleAdvertiseStop":
769
+ return { operation: "ble.advertise_stop" };
770
+ case "bleAddService": {
771
+ const uuid = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
772
+ if (uuid === null)
773
+ return null;
774
+ return { operation: "ble.add_service", uuid: quoteNonIdentifier(uuid) };
775
+ }
776
+ case "bleAddChar": {
777
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
778
+ const uuid = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
779
+ const type = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
780
+ const perms = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
781
+ const svcIndex = resolveNumericOrExpression(args, 4, instance, paramNames, callArgTexts, paramDefaults);
782
+ if (uuid === null || type === null)
783
+ return null;
784
+ // Resolve BleValueType.X → the enum's string value (e.g. Int16 → 'int16').
785
+ const resolvedType = type.startsWith("BleValueType.")
786
+ ? JSON.stringify(type.replace(/^BleValueType\./, "").toLowerCase())
787
+ : quoteNonIdentifier(type);
788
+ // Resolve BlePerm.X | BlePerm.Y → numeric bitmask.
789
+ // The | expression renders as "BlePerm.Read | BlePerm.Notify" via the
790
+ // binary-expression handler in resolveExpressionText, which keeps the
791
+ // raw text. Evaluate it by extracting member names and mapping to values.
792
+ const resolvedPerms = resolveBlePermExpr(perms);
793
+ return {
794
+ operation: "ble.add_char",
795
+ index: index ?? 0,
796
+ uuid: quoteNonIdentifier(uuid),
797
+ type: resolvedType,
798
+ perms: resolvedPerms,
799
+ svcIndex: svcIndex ?? 0,
800
+ };
801
+ }
802
+ case "bleOnRead": {
803
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
804
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
805
+ if (handler === null)
806
+ return null;
807
+ return { operation: "ble.on_read", index: index ?? 0, handler };
808
+ }
809
+ case "bleOnWrite": {
810
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
811
+ const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
812
+ if (handler === null)
813
+ return null;
814
+ return { operation: "ble.on_write", index: index ?? 0, handler };
815
+ }
816
+ case "bleOnConnect": {
817
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
818
+ if (handler === null)
819
+ return null;
820
+ return { operation: "ble.on_connect", handler };
821
+ }
822
+ case "bleOnDisconnect": {
823
+ const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
824
+ if (handler === null)
825
+ return null;
826
+ return { operation: "ble.on_disconnect", handler };
827
+ }
828
+ case "bleNotify": {
829
+ const index = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
830
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
831
+ if (value === null)
832
+ return null;
833
+ return { operation: "ble.notify", index: index ?? 0, value };
834
+ }
835
+ case "bleIsConnected":
836
+ return { operation: "ble.is_connected" };
837
+ case "bleClientCount":
838
+ return { operation: "ble.client_count" };
839
+ case "bleStatus":
840
+ return { operation: "ble.status" };
841
+ case "bleSetName": {
842
+ const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
843
+ if (name === null)
844
+ return null;
845
+ return { operation: "ble.set_name", name: quoteNonIdentifier(name) };
846
+ }
847
+ case "bleUntilConnected": {
848
+ const timeoutMs = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
849
+ return { operation: "ble.until_connected", timeoutMs, blocking: true };
850
+ }
851
+ case "bleUntilConnectedStart":
852
+ return { operation: "ble.until_connected_start" };
853
+ case "bleSetTxPower": {
854
+ const dbm = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
855
+ if (dbm === null)
856
+ return null;
857
+ return { operation: "ble.set_tx_power", dbm };
858
+ }
859
+ // ── Preferences (NVS key/value store) ──
860
+ case "preferencesBegin": {
861
+ const ns = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
862
+ const ro = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
863
+ if (ns === null)
864
+ return null;
865
+ return { operation: "preferences.begin", namespace: quoteNonIdentifier(ns), readOnly: ro === "true" };
866
+ }
867
+ case "preferencesEnd":
868
+ return { operation: "preferences.end" };
869
+ case "preferencesClear":
870
+ return { operation: "preferences.clear" };
871
+ case "preferencesRemove": {
872
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
873
+ if (key === null)
874
+ return null;
875
+ return { operation: "preferences.remove", key: quoteNonIdentifier(key) };
876
+ }
877
+ case "preferencesPutInt": {
878
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
879
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
880
+ if (key === null || value === null)
881
+ return null;
882
+ return { operation: "preferences.put_int", key: quoteNonIdentifier(key), value };
883
+ }
884
+ case "preferencesGetInt": {
885
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
886
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
887
+ if (key === null)
888
+ return null;
889
+ return { operation: "preferences.get_int", key: quoteNonIdentifier(key), defaultValue: def };
890
+ }
891
+ case "preferencesPutUInt": {
892
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
893
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
894
+ if (key === null || value === null)
895
+ return null;
896
+ return { operation: "preferences.put_uint", key: quoteNonIdentifier(key), value };
897
+ }
898
+ case "preferencesGetUInt": {
899
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
900
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
901
+ if (key === null)
902
+ return null;
903
+ return { operation: "preferences.get_uint", key: quoteNonIdentifier(key), defaultValue: def };
904
+ }
905
+ case "preferencesPutBool": {
906
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
907
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
908
+ if (key === null || value === null)
909
+ return null;
910
+ return { operation: "preferences.put_bool", key: quoteNonIdentifier(key), value: value === "true" };
911
+ }
912
+ case "preferencesGetBool": {
913
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
914
+ const def = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
915
+ if (key === null)
916
+ return null;
917
+ return { operation: "preferences.get_bool", key: quoteNonIdentifier(key), defaultValue: def === "true" };
918
+ }
919
+ case "preferencesPutFloat": {
920
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
921
+ const value = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
922
+ if (key === null || value === null)
923
+ return null;
924
+ return { operation: "preferences.put_float", key: quoteNonIdentifier(key), value };
925
+ }
926
+ case "preferencesGetFloat": {
927
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
928
+ const def = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? 0;
929
+ if (key === null)
930
+ return null;
931
+ return { operation: "preferences.get_float", key: quoteNonIdentifier(key), defaultValue: def };
932
+ }
933
+ case "preferencesPutString": {
934
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
935
+ const value = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
936
+ if (key === null || value === null)
937
+ return null;
938
+ return { operation: "preferences.put_string", key: quoteNonIdentifier(key), value: quoteNonIdentifier(value) };
939
+ }
940
+ case "preferencesGetString": {
941
+ const key = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
942
+ const def = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults) ?? '""';
943
+ if (key === null)
944
+ return null;
945
+ return { operation: "preferences.get_string", key: quoteNonIdentifier(key), defaultValue: quoteNonIdentifier(def) };
946
+ }
386
947
  // ── Interrupts ──
387
948
  case "interruptAttach": {
388
949
  const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);