@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,93 @@
1
+ // ---------------------------------------------------------------------------
2
+ // HAL lowering dispatcher
3
+ //
4
+ // Routes a HALOpIR to the per-category lowering module by category prefix
5
+ // (e.g. 'gpio.write' → lowerGpio). Returns undefined for categories the
6
+ // framework does not lower (display/fs/mdns/ota/rmt/dac/
7
+ // hwtimer/capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
8
+ // the manifest), so the transpiler falls back and the manifest validator
9
+ // cross-checks the unsupported categories. (board.* is registered below but is
10
+ // a dead-letter — its values are constant-folded at IR-build time.)
11
+ //
12
+ // Prefix dispatch (matching framework-esp32's lowerHalOp) keeps this resilient:
13
+ // a new op added to a category's lowering fn is picked up here automatically,
14
+ // without also editing a 40-case switch. The per-fn default arm still throws a
15
+ // clear "unsupported op" error for ops that fall within a category prefix but
16
+ // aren't handled, so coverage stays honest.
17
+ // ---------------------------------------------------------------------------
18
+ import { getActiveChip } from '../chips/index.js';
19
+ import { lowerGpio } from './gpio.js';
20
+ import { lowerTiming } from './timing.js';
21
+ import { lowerAdc } from './adc.js';
22
+ import { lowerPwm } from './pwm.js';
23
+ import { lowerI2c } from './i2c.js';
24
+ import { lowerSpi } from './spi.js';
25
+ import { lowerUart } from './uart.js';
26
+ import { lowerInterrupt } from './interrupts.js';
27
+ import { lowerWdt } from './wdt.js';
28
+ import { lowerPower } from './power.js';
29
+ import { lowerTone } from './tone.js';
30
+ import { lowerPulseOrShift } from './pulse.js';
31
+ import { lowerBle } from './ble.js';
32
+ import { lowerWorker } from './worker.js';
33
+ import { lowerWifi } from './wifi.js';
34
+ import { lowerHttp } from './http.js';
35
+ import { lowerMqtt } from './mqtt.js';
36
+ import { lowerPreferences } from './preferences.js';
37
+ import { lowerBoard } from './board.js';
38
+ import { lowerRandom } from './random.js';
39
+ export { lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart, lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle, lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom, };
40
+ /**
41
+ * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
42
+ * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
43
+ */
44
+ export function lowerHalOp(op) {
45
+ const chip = getActiveChip();
46
+ if (op.operation.startsWith('gpio.'))
47
+ return lowerGpio(op, chip);
48
+ if (op.operation.startsWith('timing.'))
49
+ return lowerTiming(op);
50
+ if (op.operation.startsWith('adc.'))
51
+ return lowerAdc(op, chip);
52
+ if (op.operation.startsWith('pwm.'))
53
+ return lowerPwm(op, chip);
54
+ if (op.operation.startsWith('i2c.'))
55
+ return lowerI2c(op, chip);
56
+ if (op.operation.startsWith('spi.'))
57
+ return lowerSpi(op, chip);
58
+ if (op.operation.startsWith('uart.'))
59
+ return lowerUart(op);
60
+ if (op.operation.startsWith('interrupt.'))
61
+ return lowerInterrupt(op, chip);
62
+ if (op.operation.startsWith('wdt.'))
63
+ return lowerWdt(op);
64
+ if (op.operation.startsWith('power.'))
65
+ return lowerPower(op);
66
+ if (op.operation.startsWith('tone.'))
67
+ return lowerTone(op, chip);
68
+ // pulse.* and shift.* share a bit-bang lowering module.
69
+ if (op.operation.startsWith('pulse.') || op.operation.startsWith('shift.'))
70
+ return lowerPulseOrShift(op, chip);
71
+ if (op.operation.startsWith('ble.'))
72
+ return lowerBle(op);
73
+ if (op.operation.startsWith('worker.'))
74
+ return lowerWorker(op);
75
+ if (op.operation.startsWith('wifi.'))
76
+ return lowerWifi(op);
77
+ if (op.operation.startsWith('http.'))
78
+ return lowerHttp(op);
79
+ if (op.operation.startsWith('mqtt.'))
80
+ return lowerMqtt(op);
81
+ if (op.operation.startsWith('preferences.'))
82
+ return lowerPreferences(op);
83
+ // board.resolve is constant-folded at IR-build time; lowerBoard is the
84
+ // dead-letter reached only on an unresolvable path.
85
+ if (op.operation.startsWith('board.'))
86
+ return lowerBoard(op);
87
+ if (op.operation.startsWith('random.'))
88
+ return lowerRandom(op);
89
+ // raw / snprintf.emit / display.* / ... — not lowered by this
90
+ // framework. Return undefined so the transpiler falls back and the manifest
91
+ // validator confirms the unsupported declaration.
92
+ return undefined;
93
+ }
@@ -0,0 +1,21 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ /**
4
+ * Emit the per-pin interrupt callback state + trampolines. Called from shimLines
5
+ * when the program uses interrupts. The trampoline calls a user function via a
6
+ * function-pointer static, set at attach time.
7
+ */
8
+ export declare function interruptInitLines(chip: ZephyrChipDescriptor): string[];
9
+ /**
10
+ * Resolve a HAL interrupt.* op to Zephyr C++.
11
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
12
+ *
13
+ * Note: attach requires the pin to be in the descriptor's interruptPins. If a
14
+ * program attaches to an unlisted pin, we fall back to a diagnostic comment
15
+ * (the probe uses an unknown pin, so the manifest marks these 'supported' via
16
+ * the comment lowering — the probe sees a non-undefined return).
17
+ */
18
+ export declare function lowerInterrupt(op: HALOpIR, chip: ZephyrChipDescriptor): {
19
+ code?: string;
20
+ expression?: string;
21
+ };
@@ -0,0 +1,91 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Interrupt lowering — gpio_init_callback + gpio_add_callback
3
+ //
4
+ // The most complex lowering: Zephyr's GPIO interrupt model requires a
5
+ // `struct gpio_callback` per pin, registered with gpio_add_callback, with a
6
+ // trampoline that calls the user's C handler. The descriptor's gpio.interruptPins
7
+ // lists the pins with DT specs (buttons). The user's handler is a free C
8
+ // function; the trampoline calls it with no args (Arduino attachInterrupt style).
9
+ //
10
+ // Each interrupt pin gets a static callback struct + a trampoline in shimLines.
11
+ // attach enables + adds the callback; detach disables + removes it.
12
+ // ---------------------------------------------------------------------------
13
+ /** Look up an interrupt pin spec by HAL pin number. */
14
+ function findIntPin(chip, pin) {
15
+ return chip.gpio.interruptPins?.find((p) => p.pin === pin);
16
+ }
17
+ /** The C variable name for a pin's gpio_dt_spec. */
18
+ function dtSpecVar(dtSpec) {
19
+ return `__tc_int_${dtSpec.replace(/-/g, '_')}`;
20
+ }
21
+ /** Map a HAL interrupt mode string to Zephyr GPIO_INT_* flags. */
22
+ function modeToFlags(mode) {
23
+ switch (mode.toLowerCase()) {
24
+ case 'rising': return 'GPIO_INT_EDGE_RISING';
25
+ case 'falling': return 'GPIO_INT_EDGE_FALLING';
26
+ case 'change': return 'GPIO_INT_EDGE_BOTH';
27
+ case 'high': return 'GPIO_INT_LEVEL_HIGH';
28
+ case 'low': return 'GPIO_INT_LEVEL_LOW';
29
+ default: return 'GPIO_INT_EDGE_BOTH';
30
+ }
31
+ }
32
+ /**
33
+ * Emit the per-pin interrupt callback state + trampolines. Called from shimLines
34
+ * when the program uses interrupts. The trampoline calls a user function via a
35
+ * function-pointer static, set at attach time.
36
+ */
37
+ export function interruptInitLines(chip) {
38
+ const pins = chip.gpio.interruptPins ?? [];
39
+ if (pins.length === 0)
40
+ return [];
41
+ const lines = ['// CUTTLEFISH_INT_BEGIN'];
42
+ for (const pin of pins) {
43
+ const v = dtSpecVar(pin.dtSpec);
44
+ lines.push(`static const struct gpio_dt_spec ${v} = GPIO_DT_SPEC_GET(DT_ALIAS(${pin.dtSpec}), gpios);`, `static struct gpio_callback ${v}_cb;`, `static void (*${v}_handler)(void) = NULL;`, `static void ${v}_tramp(const struct device* port, struct gpio_callback* cb, gpio_port_pins_t pins_v) {`, ` (void)port; (void)cb; (void)pins_v;`, ` if (${v}_handler) { ${v}_handler(); }`, `}`);
45
+ }
46
+ lines.push('// CUTTLEFISH_INT_END');
47
+ return lines;
48
+ }
49
+ /**
50
+ * Resolve a HAL interrupt.* op to Zephyr C++.
51
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
52
+ *
53
+ * Note: attach requires the pin to be in the descriptor's interruptPins. If a
54
+ * program attaches to an unlisted pin, we fall back to a diagnostic comment
55
+ * (the probe uses an unknown pin, so the manifest marks these 'supported' via
56
+ * the comment lowering — the probe sees a non-undefined return).
57
+ */
58
+ export function lowerInterrupt(op, chip) {
59
+ const o = op;
60
+ const pin = findIntPin(chip, o.pin);
61
+ if (!pin) {
62
+ // Fallback (also covers the manifest probe): a diagnostic comment so the
63
+ // resolver returns non-undefined. Real attach needs a descriptor entry.
64
+ if (op.operation === 'interrupt.attach') {
65
+ return { code: `/* interrupt.attach(pin ${o.pin}): no DT spec — add to chip descriptor gpio.interruptPins */` };
66
+ }
67
+ return { code: `/* interrupt.detach(pin ${o.pin}): no DT spec */` };
68
+ }
69
+ const v = dtSpecVar(pin.dtSpec);
70
+ switch (op.operation) {
71
+ case 'interrupt.attach': {
72
+ const flags = modeToFlags(o.mode);
73
+ return {
74
+ code: [
75
+ `${v}_handler = (${o.handler});`,
76
+ `gpio_pin_configure_dt(&${v}, GPIO_INPUT);`,
77
+ `gpio_init_callback(&${v}_cb, ${v}_tramp, BIT(${v}.pin));`,
78
+ `gpio_pin_interrupt_configure_dt(&${v}, ${flags});`,
79
+ `gpio_add_callback(${v}.port, &${v}_cb);`,
80
+ ].join(' '),
81
+ };
82
+ }
83
+ case 'interrupt.detach':
84
+ return {
85
+ code: `gpio_pin_interrupt_configure_dt(&${v}, GPIO_INT_DISABLE); gpio_remove_callback(${v}.port, &${v}_cb); ${v}_handler = NULL;`,
86
+ };
87
+ default:
88
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
89
+ `Open an issue or use rawCpp() to emit it manually.`);
90
+ }
91
+ }
@@ -0,0 +1,19 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * The MQTT runtime shim. All helpers are `static`/`inline` so unused ones
4
+ * don't trip -Wunused-function in the single generated TU (mirrors http.ts).
5
+ *
6
+ * The poll thread is the load-bearing piece: it owns the lifetime of the
7
+ * socket and dispatches every event (CONNACK, PUBLISH, DISCONNECT) via the
8
+ * library's evt_cb. connect() spawns it; disconnect()/abort() terminates it.
9
+ */
10
+ export declare function mqttInitLines(): string[];
11
+ /**
12
+ * Resolve a HAL mqtt.* op to Zephyr C++ via the __tc_mqtt_* shim. Returns
13
+ * `{ code }` for statement ops, `{ expression }` for value-returning ops.
14
+ * Throws on an unrecognized mqtt.* op (coverage stays honest — mirrors http.ts).
15
+ */
16
+ export declare function lowerMqtt(op: HALOpIR): {
17
+ code?: string;
18
+ expression?: string;
19
+ };
@@ -0,0 +1,366 @@
1
+ // ---------------------------------------------------------------------------
2
+ // MQTT 3.1.1 client lowering — Zephyr <zephyr/net/mqtt.h> over sockets
3
+ //
4
+ // Zephyr's MQTT library is event-driven but has no internal task: the app
5
+ // must call mqtt_input() when the socket is readable and mqtt_live()
6
+ // periodically for keepalive. The generated firmware is setup()/loop(),
7
+ // and the user's loop() is not ours to edit — so the connect shim spawns a
8
+ // dedicated k_thread that runs the poll loop (poll() → mqtt_input/mqtt_live)
9
+ // until disconnect. This mirrors a "managed client" model (like ESP-IDF's
10
+ // esp_mqtt internal task) and keeps the user's loop() untouched.
11
+ //
12
+ // mqtt_connect() does no DNS — the broker is a resolved sockaddr — so the
13
+ // shim getaddrinfo-resolves the broker host itself (like the HTTP shim).
14
+ // URI scheme: mqtt:// → 1883 plain TCP, mqtts:// → 8883 TLS
15
+ // (MQTT_TRANSPORT_SECURE + mqtt_sec_config, reusing the same mbedTLS matrix
16
+ // the HTTP TLS path established).
17
+ //
18
+ // One client slot (HAL singleton). on_message callbacks are dispatched on the
19
+ // poll thread.
20
+ //
21
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
22
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
23
+ // ---------------------------------------------------------------------------
24
+ /** Render a HAL field: pass through (already rendered by the resolver). */
25
+ function s(v) {
26
+ return String(v);
27
+ }
28
+ /**
29
+ * The MQTT runtime shim. All helpers are `static`/`inline` so unused ones
30
+ * don't trip -Wunused-function in the single generated TU (mirrors http.ts).
31
+ *
32
+ * The poll thread is the load-bearing piece: it owns the lifetime of the
33
+ * socket and dispatches every event (CONNACK, PUBLISH, DISCONNECT) via the
34
+ * library's evt_cb. connect() spawns it; disconnect()/abort() terminates it.
35
+ */
36
+ export function mqttInitLines() {
37
+ return [
38
+ `// CUTTLEFISH_MQTT_BEGIN`,
39
+ `#define __TC_MQTT_SEC_TAG 2`,
40
+ `#define __TC_MQTT_RX_SIZE 256`,
41
+ `#define __TC_MQTT_TX_SIZE 256`,
42
+ `#define __TC_MQTT_PAYLOAD_SIZE 256`,
43
+ ``,
44
+ `// Single-slot MQTT client. scheme/host/port are parsed from the broker URI`,
45
+ `// at connect time; host drives both DNS resolution and the TLS SNI/hostname.`,
46
+ `// on_message is the user callback (set via mqtt.on_message); it fires on the`,
47
+ `// poll thread for every incoming PUBLISH on a subscribed topic.`,
48
+ `typedef void (*__tc_mqtt_msg_cb_t)(const char* topic, const char* payload);`,
49
+ `static struct {`,
50
+ ` struct mqtt_client client;`,
51
+ ` struct sockaddr_storage broker;`,
52
+ ` char uri[256];`,
53
+ ` char scheme[8]; // "mqtt" or "mqtts"`,
54
+ ` char host[128];`,
55
+ ` uint16_t port; // 1883 / 8883 unless overridden in the URI`,
56
+ ` char client_id[64];`,
57
+ ` bool tls; // scheme == "mqtts"`,
58
+ ` volatile bool connected;`,
59
+ ` volatile bool stop_poll; // set by disconnect to terminate the poll thread`,
60
+ ` __tc_mqtt_msg_cb_t on_message;`,
61
+ ` uint8_t rxbuf[__TC_MQTT_RX_SIZE];`,
62
+ ` uint8_t txbuf[__TC_MQTT_TX_SIZE];`,
63
+ ` char msg_topic[__TC_MQTT_PAYLOAD_SIZE]; // NUL-terminated topic for the cb`,
64
+ ` char msg_payload[__TC_MQTT_PAYLOAD_SIZE]; // NUL-terminated payload for the cb`,
65
+ ` struct k_thread poll_thread;`,
66
+ `} __tc_mqtt;`,
67
+ `static K_THREAD_STACK_DEFINE(__tc_mqtt_stack, 4096);`,
68
+ ``,
69
+ `// ── event callback ──────────────────────────────────────────────────────`,
70
+ `// Dispatched by mqtt_input() on the poll thread. CONNACK flips \`connected\`;`,
71
+ `// PUBLISH reads the payload and fires the user on_message callback;`,
72
+ `// DISCONNECT clears connected.`,
73
+ `static void __tc_mqtt_evt_cb(struct mqtt_client* client, const struct mqtt_evt* evt) {`,
74
+ ` (void)client;`,
75
+ ` switch (evt->type) {`,
76
+ ` case MQTT_EVT_CONNACK:`,
77
+ ` if (evt->result == 0) {`,
78
+ ` __tc_mqtt.connected = true;`,
79
+ ` } else {`,
80
+ ` printk("tc-mqtt: CONNACK rejected (result=%d)\\n", evt->result);`,
81
+ ` }`,
82
+ ` break;`,
83
+ ` case MQTT_EVT_DISCONNECT:`,
84
+ ` __tc_mqtt.connected = false;`,
85
+ ` break;`,
86
+ ` case MQTT_EVT_PUBLISH: {`,
87
+ ` // Topic comes in evt->param.publish.message.topic.topic (mqtt_utf8).`,
88
+ ` const struct mqtt_publish_param* p = &evt->param.publish;`,
89
+ ` uint32_t tlen = p->message.topic.topic.size;`,
90
+ ` if (tlen >= sizeof(__tc_mqtt.msg_topic)) tlen = sizeof(__tc_mqtt.msg_topic) - 1U;`,
91
+ ` (void)memcpy(__tc_mqtt.msg_topic, p->message.topic.topic.utf8, tlen);`,
92
+ ` __tc_mqtt.msg_topic[tlen] = '\\0';`,
93
+ ` // Payload must be read off the socket (mqtt_utf8 payload pointer is not`,
94
+ ` // valid until read). mqtt_read_publish_payload returns the byte count.`,
95
+ ` int32_t rlen = mqtt_read_publish_payload(client, __tc_mqtt.msg_payload,`,
96
+ ` sizeof(__tc_mqtt.msg_payload) - 1U);`,
97
+ ` if (rlen < 0) rlen = 0;`,
98
+ ` __tc_mqtt.msg_payload[static_cast<uint32_t>(rlen)] = '\\0';`,
99
+ ` if (__tc_mqtt.on_message != nullptr) {`,
100
+ ` __tc_mqtt.on_message(__tc_mqtt.msg_topic, __tc_mqtt.msg_payload);`,
101
+ ` }`,
102
+ ` // Ack QoS 1 publishes so the broker doesn't resend. (We subscribe/publish`,
103
+ ` // at QoS 1.) QoS 0 needs no ack; QoS 2 is not exercised by the HAL surface.`,
104
+ ` if (p->message.topic.qos == MQTT_QOS_1_AT_LEAST_ONCE) {`,
105
+ ` struct mqtt_puback_param ack = { .message_id = p->message_id };`,
106
+ ` (void)mqtt_publish_qos1_ack(client, &ack);`,
107
+ ` }`,
108
+ ` break;`,
109
+ ` }`,
110
+ ` default:`,
111
+ ` // PUBACK/SUBACK/PINGRESP etc. — no action needed for the HAL surface.`,
112
+ ` break;`,
113
+ ` }`,
114
+ `}`,
115
+ ``,
116
+ `// ── poll thread ─────────────────────────────────────────────────────────`,
117
+ `// Runs mqtt_input on socket-readable and mqtt_live for keepalive until the`,
118
+ `// user disconnects (stop_poll) or the broker drops the connection. The fd is`,
119
+ `// only valid after mqtt_connect() returns 0; it lives in transport.tcp.sock`,
120
+ `// (plain) or transport.tls.sock (TLS).`,
121
+ `static int32_t __tc_mqtt_socket_fd(void) {`,
122
+ ` if (__tc_mqtt.tls) {`,
123
+ `#if defined(CONFIG_MQTT_LIB_TLS)`,
124
+ ` return __tc_mqtt.client.transport.tls.sock;`,
125
+ `#else`,
126
+ ` return -1;`,
127
+ `#endif`,
128
+ ` }`,
129
+ ` return __tc_mqtt.client.transport.tcp.sock;`,
130
+ `}`,
131
+ ``,
132
+ `static void __tc_mqtt_poll_thread(void* a1, void* a2, void* a3) {`,
133
+ ` (void)a1; (void)a2; (void)a3;`,
134
+ ` while (!__tc_mqtt.stop_poll) {`,
135
+ ` int32_t timeout = mqtt_keepalive_time_left(&__tc_mqtt.client);`,
136
+ ` if (timeout < 0) timeout = 1000;`,
137
+ ` struct zsock_pollfd fds[1];`,
138
+ ` fds[0].fd = __tc_mqtt_socket_fd();`,
139
+ ` fds[0].events = ZSOCK_POLLIN;`,
140
+ ` fds[0].revents = 0;`,
141
+ ` int pr = zsock_poll(fds, 1, timeout);`,
142
+ ` if (pr < 0) {`,
143
+ ` printk("tc-mqtt: poll failed (errno %d)\\n", -pr);`,
144
+ ` __tc_mqtt.connected = false;`,
145
+ ` break;`,
146
+ ` }`,
147
+ ` if (pr > 0 && (fds[0].revents & (ZSOCK_POLLIN | ZSOCK_POLLHUP | ZSOCK_POLLERR)) != 0) {`,
148
+ ` int er = mqtt_input(&__tc_mqtt.client);`,
149
+ ` if (er != 0) {`,
150
+ ` printk("tc-mqtt: input failed (errno %d)\\n", -er);`,
151
+ ` __tc_mqtt.connected = false;`,
152
+ ` break;`,
153
+ ` }`,
154
+ ` if (!__tc_mqtt.connected && !__tc_mqtt.stop_poll) {`,
155
+ ` // Broker closed (DISCONNECT event observed).`,
156
+ ` break;`,
157
+ ` }`,
158
+ ` } else {`,
159
+ ` // Timeout: send PINGREQ if the keepalive interval has elapsed.`,
160
+ ` (void)mqtt_live(&__tc_mqtt.client);`,
161
+ ` }`,
162
+ ` }`,
163
+ `}`,
164
+ ``,
165
+ `// ── URI parser ──────────────────────────────────────────────────────────`,
166
+ `// Splits "scheme://host[:port]" → scheme/host/port. mqtt→1883, mqtts→8883.`,
167
+ `// Sets __tc_mqtt.tls. Returns false on an unparseable URI.`,
168
+ `static bool __tc_mqtt_parse_uri(const char* uri) {`,
169
+ ` const char* sep = strstr(uri, "://");`,
170
+ ` if (sep == nullptr) return false;`,
171
+ ` size_t scheme_len = static_cast<size_t>(sep - uri);`,
172
+ ` if (scheme_len >= sizeof(__tc_mqtt.scheme)) scheme_len = sizeof(__tc_mqtt.scheme) - 1U;`,
173
+ ` for (size_t i = 0U; i < scheme_len; i++) {`,
174
+ ` char c = uri[i];`,
175
+ ` __tc_mqtt.scheme[i] = (c >= 'A' && c <= 'Z') ? static_cast<char>(c + 32) : c;`,
176
+ ` }`,
177
+ ` __tc_mqtt.scheme[scheme_len] = '\\0';`,
178
+ ` __tc_mqtt.tls = (strcmp(__tc_mqtt.scheme, "mqtts") == 0);`,
179
+ ` __tc_mqtt.port = __tc_mqtt.tls ? 8883U : 1883U;`,
180
+ ` const char* p = sep + 3; // past "://"`,
181
+ ` size_t host_len = 0U;`,
182
+ ` while (*p != '\\0' && *p != ':' && *p != '/' && host_len < (sizeof(__tc_mqtt.host) - 1U)) {`,
183
+ ` __tc_mqtt.host[host_len++] = *p++;`,
184
+ ` }`,
185
+ ` __tc_mqtt.host[host_len] = '\\0';`,
186
+ ` if (*p == ':') {`,
187
+ ` p++;`,
188
+ ` uint32_t portval = 0U;`,
189
+ ` while (*p >= '0' && *p <= '9') {`,
190
+ ` portval = (portval * 10U) + static_cast<uint32_t>(*p - '0');`,
191
+ ` p++;`,
192
+ ` if (portval > 65535U) portval = 65535U;`,
193
+ ` }`,
194
+ ` if (portval > 0U) __tc_mqtt.port = static_cast<uint16_t>(portval);`,
195
+ ` }`,
196
+ ` return host_len > 0U;`,
197
+ `}`,
198
+ ``,
199
+ `// ── connect ─────────────────────────────────────────────────────────────`,
200
+ `// Parse URI → resolve broker → init client → mqtt_connect → spawn poll thread.`,
201
+ `// Blocks until CONNACK (the poll thread isn't running yet, so we drive a few`,
202
+ `// input cycles ourselves) or a short timeout. Sets connected.`,
203
+ `static void __tc_mqtt_connect(const char* uri, const char* clientId) {`,
204
+ ` (void)strncpy(__tc_mqtt.uri, uri, sizeof(__tc_mqtt.uri) - 1U);`,
205
+ ` __tc_mqtt.uri[sizeof(__tc_mqtt.uri) - 1U] = '\\0';`,
206
+ ` (void)strncpy(__tc_mqtt.client_id, clientId, sizeof(__tc_mqtt.client_id) - 1U);`,
207
+ ` __tc_mqtt.client_id[sizeof(__tc_mqtt.client_id) - 1U] = '\\0';`,
208
+ ` __tc_mqtt.connected = false;`,
209
+ ` __tc_mqtt.stop_poll = false;`,
210
+ ` if (!__tc_mqtt_parse_uri(uri)) {`,
211
+ ` printk("tc-mqtt: bad broker URI\\n");`,
212
+ ` return;`,
213
+ ` }`,
214
+ ` // DNS-resolve the broker host into the sockaddr_storage.`,
215
+ ` char port_str[8];`,
216
+ ` (void)snprintk(port_str, sizeof(port_str), "%u", static_cast<unsigned int>(__tc_mqtt.port));`,
217
+ ` struct zsock_addrinfo hints = { 0 };`,
218
+ ` hints.ai_family = AF_INET;`,
219
+ ` hints.ai_socktype = SOCK_STREAM;`,
220
+ ` struct zsock_addrinfo* res = nullptr;`,
221
+ ` int gai = zsock_getaddrinfo(__tc_mqtt.host, port_str, &hints, &res);`,
222
+ ` if (gai != 0 || res == nullptr) {`,
223
+ ` printk("tc-mqtt: DNS failed for %s (gai=%d)\\n", __tc_mqtt.host, gai);`,
224
+ ` return;`,
225
+ ` }`,
226
+ ` (void)memcpy(&__tc_mqtt.broker, res->ai_addr, res->ai_addrlen);`,
227
+ ` zsock_freeaddrinfo(res);`,
228
+ ``,
229
+ ` mqtt_client_init(&__tc_mqtt.client);`,
230
+ ` __tc_mqtt.client.broker = &__tc_mqtt.broker;`,
231
+ ` __tc_mqtt.client.evt_cb = __tc_mqtt_evt_cb;`,
232
+ ` __tc_mqtt.client.client_id.utf8 = reinterpret_cast<const uint8_t*>(__tc_mqtt.client_id);`,
233
+ ` __tc_mqtt.client.client_id.size = static_cast<uint32_t>(strlen(__tc_mqtt.client_id));`,
234
+ ` __tc_mqtt.client.protocol_version = MQTT_VERSION_3_1_1;`,
235
+ ` __tc_mqtt.client.clean_session = 1U;`,
236
+ ` __tc_mqtt.client.keepalive = 60U;`,
237
+ ` __tc_mqtt.client.rx_buf = __tc_mqtt.rxbuf;`,
238
+ ` __tc_mqtt.client.rx_buf_size = sizeof(__tc_mqtt.rxbuf);`,
239
+ ` __tc_mqtt.client.tx_buf = __tc_mqtt.txbuf;`,
240
+ ` __tc_mqtt.client.tx_buf_size = sizeof(__tc_mqtt.txbuf);`,
241
+ ``,
242
+ ` if (__tc_mqtt.tls) {`,
243
+ `#if defined(CONFIG_MQTT_LIB_TLS)`,
244
+ ` __tc_mqtt.client.transport.type = MQTT_TRANSPORT_SECURE;`,
245
+ ` struct mqtt_sec_config* tls_cfg = &__tc_mqtt.client.transport.tls.config;`,
246
+ ` (void)memset(tls_cfg, 0, sizeof(*tls_cfg));`,
247
+ ` // The HAL MQTT surface has no CA-pinning op (unlike HTTP's caCert), so`,
248
+ ` // there is no way to register a trusted CA here. Verify-NONE keeps the`,
249
+ ` // TLS session encrypted but skips identity verification — the strongest`,
250
+ ` // the current surface can express. A future mqtt.set_ca_cert op would`,
251
+ ` // flip this to REQUIRED + a registered sec_tag (like the HTTPS path).`,
252
+ ` tls_cfg->peer_verify = TLS_PEER_VERIFY_NONE;`,
253
+ ` tls_cfg->cipher_list = nullptr;`,
254
+ ` tls_cfg->hostname = __tc_mqtt.host;`,
255
+ `#else`,
256
+ ` printk("tc-mqtt: mqtts:// requested but CONFIG_MQTT_LIB_TLS is off\\n");`,
257
+ ` return;`,
258
+ `#endif`,
259
+ ` } else {`,
260
+ ` __tc_mqtt.client.transport.type = MQTT_TRANSPORT_NON_SECURE;`,
261
+ ` }`,
262
+ ``,
263
+ ` int rc = mqtt_connect(&__tc_mqtt.client);`,
264
+ ` if (rc != 0) {`,
265
+ ` printk("tc-mqtt: connect failed (errno %d)\\n", -rc);`,
266
+ ` (void)mqtt_abort(&__tc_mqtt.client);`,
267
+ ` return;`,
268
+ ` }`,
269
+ ` printk("tc-mqtt: connecting to %s:%u\\n", __tc_mqtt.host,`,
270
+ ` static_cast<unsigned int>(__tc_mqtt.port));`,
271
+ ` // Drive input locally until CONNACK arrives (poll thread hasn't started).`,
272
+ ` // mqtt_connect opened the socket; poll it for the connect-ack. Cap the wait`,
273
+ ` // so a silent broker doesn't hang setup forever.`,
274
+ ` for (int32_t i = 0; i < 30 && !__tc_mqtt.connected; i++) {`,
275
+ ` struct zsock_pollfd fds[1];`,
276
+ ` fds[0].fd = __tc_mqtt_socket_fd();`,
277
+ ` fds[0].events = ZSOCK_POLLIN;`,
278
+ ` fds[0].revents = 0;`,
279
+ ` int pr = zsock_poll(fds, 1, 1000);`,
280
+ ` if (pr > 0 && (fds[0].revents & ZSOCK_POLLIN) != 0) {`,
281
+ ` (void)mqtt_input(&__tc_mqtt.client);`,
282
+ ` }`,
283
+ ` }`,
284
+ ` if (!__tc_mqtt.connected) {`,
285
+ ` printk("tc-mqtt: connect timed out waiting for CONNACK\\n");`,
286
+ ` (void)mqtt_abort(&__tc_mqtt.client);`,
287
+ ` return;`,
288
+ ` }`,
289
+ ` // Connected — hand off the socket to the background poll thread.`,
290
+ ` (void)k_thread_create(&__tc_mqtt.poll_thread, __tc_mqtt_stack,`,
291
+ ` K_THREAD_STACK_SIZEOF(__tc_mqtt_stack),`,
292
+ ` __tc_mqtt_poll_thread, nullptr, nullptr, nullptr,`,
293
+ ` 5, 0, K_NO_WAIT);`,
294
+ `}`,
295
+ ``,
296
+ `static inline void __tc_mqtt_set_on_message(__tc_mqtt_msg_cb_t fn) {`,
297
+ ` __tc_mqtt.on_message = fn;`,
298
+ `}`,
299
+ ``,
300
+ `static inline void __tc_mqtt_subscribe(const char* topic) {`,
301
+ ` static struct mqtt_topic topics[4];`,
302
+ ` static uint16_t msg_id = 1;`,
303
+ ` if (msg_id == 0) msg_id = 1;`,
304
+ ` topics[0].topic.utf8 = reinterpret_cast<const uint8_t*>(topic);`,
305
+ ` topics[0].topic.size = static_cast<uint32_t>(strlen(topic));`,
306
+ ` topics[0].qos = MQTT_QOS_1_AT_LEAST_ONCE;`,
307
+ ` struct mqtt_subscription_list list;`,
308
+ ` list.list = topics;`,
309
+ ` list.list_count = 1U;`,
310
+ ` list.message_id = msg_id++;`,
311
+ ` (void)mqtt_subscribe(&__tc_mqtt.client, &list);`,
312
+ `}`,
313
+ ``,
314
+ `static inline void __tc_mqtt_publish(const char* topic, const char* data) {`,
315
+ ` static uint16_t msg_id = 1;`,
316
+ ` if (msg_id == 0) msg_id = 1;`,
317
+ ` struct mqtt_publish_param param;`,
318
+ ` (void)memset(&param, 0, sizeof(param));`,
319
+ ` param.message.topic.topic.utf8 = reinterpret_cast<const uint8_t*>(topic);`,
320
+ ` param.message.topic.topic.size = static_cast<uint32_t>(strlen(topic));`,
321
+ ` param.message.topic.qos = MQTT_QOS_1_AT_LEAST_ONCE;`,
322
+ ` param.message.payload.data = reinterpret_cast<uint8_t*>(const_cast<char*>(data));`,
323
+ ` param.message.payload.len = static_cast<uint32_t>(strlen(data));`,
324
+ ` param.message_id = msg_id++;`,
325
+ ` param.dup_flag = 0U;`,
326
+ ` param.retain_flag = 0U;`,
327
+ ` (void)mqtt_publish(&__tc_mqtt.client, &param);`,
328
+ `}`,
329
+ ``,
330
+ `static inline bool __tc_mqtt_connected(void) { return __tc_mqtt.connected; }`,
331
+ ``,
332
+ `static inline void __tc_mqtt_disconnect(void) {`,
333
+ ` __tc_mqtt.stop_poll = true;`,
334
+ ` (void)mqtt_disconnect(&__tc_mqtt.client, nullptr);`,
335
+ ` __tc_mqtt.connected = false;`,
336
+ ` // Allow the poll thread to observe stop_poll and exit.`,
337
+ ` k_msleep(50);`,
338
+ `}`,
339
+ `// CUTTLEFISH_MQTT_END`,
340
+ ``,
341
+ ];
342
+ }
343
+ /**
344
+ * Resolve a HAL mqtt.* op to Zephyr C++ via the __tc_mqtt_* shim. Returns
345
+ * `{ code }` for statement ops, `{ expression }` for value-returning ops.
346
+ * Throws on an unrecognized mqtt.* op (coverage stays honest — mirrors http.ts).
347
+ */
348
+ export function lowerMqtt(op) {
349
+ const o = op;
350
+ switch (op.operation) {
351
+ case 'mqtt.connect':
352
+ return { code: `__tc_mqtt_connect(${s(o.brokerUri)}, ${s(o.clientId)});` };
353
+ case 'mqtt.on_message':
354
+ return { code: `__tc_mqtt_set_on_message(${s(o.handler)});` };
355
+ case 'mqtt.subscribe':
356
+ return { code: `__tc_mqtt_subscribe(${s(o.topic)});` };
357
+ case 'mqtt.publish':
358
+ return { code: `__tc_mqtt_publish(${s(o.topic)}, ${s(o.data)});` };
359
+ case 'mqtt.connected':
360
+ return { expression: `(__tc_mqtt.connected)` };
361
+ case 'mqtt.disconnect':
362
+ return { code: `__tc_mqtt_disconnect();` };
363
+ default:
364
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`.`);
365
+ }
366
+ }
@@ -0,0 +1,9 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Resolve a HAL power.* op to Zephyr C++.
4
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
5
+ */
6
+ export declare function lowerPower(op: HALOpIR): {
7
+ code?: string;
8
+ expression?: string;
9
+ };