@mikrojs/firmware 0.0.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/bin/idf.py +7 -0
  4. package/chips.json +3 -0
  5. package/cmake.js +9 -0
  6. package/components/mikrojs/CMakeLists.txt +187 -0
  7. package/components/mikrojs/Kconfig +55 -0
  8. package/components/mikrojs/idf_component.yml +6 -0
  9. package/components/mikrojs/include/mem.h +3 -0
  10. package/components/mikrojs/include/mik_color.h +3 -0
  11. package/components/mikrojs/include/mik_http_internal.h +77 -0
  12. package/components/mikrojs/include/mikrojs.h +5 -0
  13. package/components/mikrojs/include/mikrojs_esp32.h +65 -0
  14. package/components/mikrojs/include/private.h +10 -0
  15. package/components/mikrojs/include/utils.h +3 -0
  16. package/components/mikrojs/mik_ble.cpp +1588 -0
  17. package/components/mikrojs/mik_ble_c_shim.c +61 -0
  18. package/components/mikrojs/mik_ble_c_shim.h +37 -0
  19. package/components/mikrojs/mik_config.cpp +167 -0
  20. package/components/mikrojs/mik_deploy.cpp +584 -0
  21. package/components/mikrojs/mik_http.cpp +916 -0
  22. package/components/mikrojs/mik_i2c.cpp +364 -0
  23. package/components/mikrojs/mik_main.cpp +542 -0
  24. package/components/mikrojs/mik_neopixel.cpp +437 -0
  25. package/components/mikrojs/mik_nvs_kv.cpp +219 -0
  26. package/components/mikrojs/mik_pin.cpp +195 -0
  27. package/components/mikrojs/mik_pwm.cpp +525 -0
  28. package/components/mikrojs/mik_recovery.cpp +86 -0
  29. package/components/mikrojs/mik_rtc.cpp +305 -0
  30. package/components/mikrojs/mik_serial_io.cpp +362 -0
  31. package/components/mikrojs/mik_sleep.cpp +226 -0
  32. package/components/mikrojs/mik_sntp.cpp +275 -0
  33. package/components/mikrojs/mik_spi.cpp +330 -0
  34. package/components/mikrojs/mik_uart.cpp +497 -0
  35. package/components/mikrojs/mik_wifi.cpp +1434 -0
  36. package/components/mikrojs/platform_esp32.cpp +192 -0
  37. package/components/mikrojs/test/CMakeLists.txt +32 -0
  38. package/components/mikrojs/test/abort_test.cpp +254 -0
  39. package/components/mikrojs/test/ble_test.cpp +714 -0
  40. package/components/mikrojs/test/fs_js_test.cpp +458 -0
  41. package/components/mikrojs/test/fs_pub_test.cpp +312 -0
  42. package/components/mikrojs/test/http_test.cpp +475 -0
  43. package/components/mikrojs/test/i2c_test.cpp +138 -0
  44. package/components/mikrojs/test/modules_extended_test.cpp +137 -0
  45. package/components/mikrojs/test/modules_test.cpp +131 -0
  46. package/components/mikrojs/test/pins_test.cpp +47 -0
  47. package/components/mikrojs/test/pwm_test.cpp +166 -0
  48. package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
  49. package/components/mikrojs/test/rtc_test.cpp +331 -0
  50. package/components/mikrojs/test/runtime_test.cpp +89 -0
  51. package/components/mikrojs/test/sleep_test.cpp +222 -0
  52. package/components/mikrojs/test/sntp_test.cpp +249 -0
  53. package/components/mikrojs/test/stdio_test.cpp +449 -0
  54. package/components/mikrojs/test/sys_test.cpp +165 -0
  55. package/components/mikrojs/test/text_encoding_test.cpp +224 -0
  56. package/components/mikrojs/test/timers_js_test.cpp +244 -0
  57. package/components/mikrojs/test/timers_test.cpp +79 -0
  58. package/components/mikrojs/test/wifi_test.cpp +599 -0
  59. package/default-app/main/CMakeLists.txt +3 -0
  60. package/default-app/main/main.cpp +5 -0
  61. package/discover.js +77 -0
  62. package/index.d.ts +7 -0
  63. package/index.js +20 -0
  64. package/package.json +61 -0
  65. package/partitions.csv +5 -0
  66. package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
  67. package/prebuilds/esp32/flasher_args.json +24 -0
  68. package/prebuilds/esp32/mikrojs.bin +0 -0
  69. package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
  70. package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
  71. package/prebuilds/esp32c3/flasher_args.json +24 -0
  72. package/prebuilds/esp32c3/mikrojs.bin +0 -0
  73. package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
  74. package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
  75. package/prebuilds/esp32c6/flasher_args.json +24 -0
  76. package/prebuilds/esp32c6/mikrojs.bin +0 -0
  77. package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
  78. package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
  79. package/prebuilds/esp32s3/flasher_args.json +24 -0
  80. package/prebuilds/esp32s3/mikrojs.bin +0 -0
  81. package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
  82. package/project.cmake +101 -0
  83. package/resolve.js +54 -0
  84. package/sdkconfig.defaults +127 -0
  85. package/sdkconfig.defaults.esp32 +8 -0
  86. package/sdkconfig.defaults.esp32c3 +15 -0
  87. package/sdkconfig.defaults.esp32c6 +26 -0
  88. package/sdkconfig.defaults.esp32s3 +22 -0
@@ -0,0 +1,497 @@
1
+ #include <cstring>
2
+
3
+ #include "driver/uart.h"
4
+ #include "mikrojs/mikrojs.h"
5
+ #include "mikrojs/private.h"
6
+ #include "mikrojs/utils.h"
7
+
8
+ #define MIK_UART_TAG "native:uart"
9
+ #define MIK_UART_RX_BUF_SIZE 2048
10
+ #define MIK_UART_MAX_INSTANCES 3
11
+
12
+ static JSClassID mik_uart_class_id;
13
+ static int mik__uart_slot = -1;
14
+
15
+ /* ── State ────────────────────────────────────────────────────────── */
16
+
17
+ struct MIKUartState {
18
+ uart_port_t port;
19
+ int32_t tx_pin; // -1 if RX-only
20
+ int32_t rx_pin; // -1 if TX-only
21
+ int32_t baud_rate;
22
+ bool begun;
23
+ bool reading; // active read() iterator exists
24
+ MIKPromise read_promise; // pending next() promise (when waiting for data)
25
+ };
26
+
27
+ /* Per-runtime tracking of all Uart instances for the loop consumer */
28
+ struct MIKUartSlot {
29
+ MIKUartState* instances[MIK_UART_MAX_INSTANCES];
30
+ int count;
31
+ };
32
+
33
+ static inline MIKUartSlot*& mik__uart_slot_data(MIKRuntime* rt) {
34
+ return reinterpret_cast<MIKUartSlot*&>(rt->module_data[mik__uart_slot]);
35
+ }
36
+
37
+ static void mik__uart_track(JSContext* ctx, MIKUartState* s) {
38
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
39
+ auto* slot = mik__uart_slot_data(mik_rt);
40
+ if (!slot) return;
41
+ if (slot->count >= MIK_UART_MAX_INSTANCES) return;
42
+ slot->instances[slot->count++] = s;
43
+ }
44
+
45
+ static void mik__uart_untrack(JSContext* ctx, MIKUartState* s) {
46
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
47
+ auto* slot = mik__uart_slot_data(mik_rt);
48
+ if (!slot) return;
49
+ for (int i = 0; i < slot->count; i++) {
50
+ if (slot->instances[i] == s) {
51
+ slot->instances[i] = slot->instances[--slot->count];
52
+ return;
53
+ }
54
+ }
55
+ }
56
+
57
+ /* ── Helpers ──────────────────────────────────────────────────────── */
58
+
59
+ static MIKUartState* mik__uart_get(JSContext* ctx, JSValue this_val) {
60
+ return static_cast<MIKUartState*>(JS_GetOpaque2(ctx, this_val, mik_uart_class_id));
61
+ }
62
+
63
+ /* ── Finalizer ────────────────────────────────────────────────────── */
64
+
65
+ static void mik__uart_finalizer(JSRuntime* rt, JSValue val) {
66
+ auto* s = static_cast<MIKUartState*>(JS_GetOpaque(val, mik_uart_class_id));
67
+ if (!s) return;
68
+ if (s->begun) {
69
+ uart_driver_delete(s->port);
70
+ }
71
+ /* Note: can't untrack here (no JSContext), but destroy handles cleanup */
72
+ free(s);
73
+ }
74
+
75
+ static JSClassDef mik_uart_class = {
76
+ .class_name = "Uart",
77
+ .finalizer = mik__uart_finalizer,
78
+ };
79
+
80
+ /* ── Constructor ──────────────────────────────────────────────────── */
81
+
82
+ static JSValue js_uart_constructor(JSContext* ctx, JSValue new_target, int argc, JSValue* argv) {
83
+ if (argc < 2) return JS_ThrowTypeError(ctx, "Uart requires port and options arguments");
84
+
85
+ int32_t port;
86
+ if (JS_ToInt32(ctx, &port, argv[0])) return JS_EXCEPTION;
87
+ if (port < 0 || port >= UART_NUM_MAX)
88
+ return JS_ThrowRangeError(ctx, "port must be 0..%d", UART_NUM_MAX - 1);
89
+
90
+ if (!JS_IsObject(argv[1])) return JS_ThrowTypeError(ctx, "Uart options must be an object");
91
+
92
+ auto* s = static_cast<MIKUartState*>(calloc(1, sizeof(MIKUartState)));
93
+ if (!s) return JS_ThrowOutOfMemory(ctx);
94
+
95
+ s->port = static_cast<uart_port_t>(port);
96
+ s->tx_pin = -1;
97
+ s->rx_pin = -1;
98
+ s->baud_rate = 115200;
99
+ s->begun = false;
100
+ s->reading = false;
101
+ s->read_promise.p = JS_UNDEFINED;
102
+ s->read_promise.rfuncs[0] = JS_UNDEFINED;
103
+ s->read_promise.rfuncs[1] = JS_UNDEFINED;
104
+
105
+ JSValue opts = argv[1];
106
+ JSValue v;
107
+
108
+ v = JS_GetPropertyStr(ctx, opts, "tx");
109
+ if (!JS_IsUndefined(v)) {
110
+ if (JS_ToInt32(ctx, &s->tx_pin, v)) {
111
+ JS_FreeValue(ctx, v);
112
+ free(s);
113
+ return JS_EXCEPTION;
114
+ }
115
+ }
116
+ JS_FreeValue(ctx, v);
117
+
118
+ v = JS_GetPropertyStr(ctx, opts, "rx");
119
+ if (!JS_IsUndefined(v)) {
120
+ if (JS_ToInt32(ctx, &s->rx_pin, v)) {
121
+ JS_FreeValue(ctx, v);
122
+ free(s);
123
+ return JS_EXCEPTION;
124
+ }
125
+ }
126
+ JS_FreeValue(ctx, v);
127
+
128
+ /* At least one pin must be provided */
129
+ if (s->tx_pin < 0 && s->rx_pin < 0) {
130
+ free(s);
131
+ return JS_ThrowTypeError(ctx, "Uart requires at least one of tx or rx pins");
132
+ }
133
+
134
+ v = JS_GetPropertyStr(ctx, opts, "baudRate");
135
+ if (!JS_IsUndefined(v)) {
136
+ if (JS_ToInt32(ctx, &s->baud_rate, v)) {
137
+ JS_FreeValue(ctx, v);
138
+ free(s);
139
+ return JS_EXCEPTION;
140
+ }
141
+ }
142
+ JS_FreeValue(ctx, v);
143
+
144
+ JSValue obj = JS_NewObjectClass(ctx, mik_uart_class_id);
145
+ if (JS_IsException(obj)) {
146
+ free(s);
147
+ return obj;
148
+ }
149
+ JS_SetOpaque(obj, s);
150
+ return obj;
151
+ }
152
+
153
+ /* ── Methods ──────────────────────────────────────────────────────── */
154
+
155
+ static JSValue js_uart_begin(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
156
+ auto* s = mik__uart_get(ctx, this_val);
157
+ if (!s) return JS_EXCEPTION;
158
+ if (s->begun) return mik__result_ok_void(ctx); // idempotent
159
+
160
+ uart_config_t uart_config = {};
161
+ uart_config.baud_rate = s->baud_rate;
162
+ uart_config.data_bits = UART_DATA_8_BITS;
163
+ uart_config.parity = UART_PARITY_DISABLE;
164
+ uart_config.stop_bits = UART_STOP_BITS_1;
165
+ uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
166
+ uart_config.source_clk = UART_SCLK_DEFAULT;
167
+
168
+ esp_err_t err = uart_param_config(s->port, &uart_config);
169
+ if (err != ESP_OK)
170
+ return mik__result_err_named(ctx, "InvalidParam",
171
+ "uart_param_config failed on port %d: %s", s->port,
172
+ esp_err_to_name(err));
173
+
174
+ err = uart_set_pin(s->port,
175
+ s->tx_pin >= 0 ? s->tx_pin : UART_PIN_NO_CHANGE,
176
+ s->rx_pin >= 0 ? s->rx_pin : UART_PIN_NO_CHANGE,
177
+ UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
178
+ if (err != ESP_OK)
179
+ return mik__result_err_named(ctx, "SetPinFailed",
180
+ "uart_set_pin failed on port %d (tx=%d, rx=%d): %s", s->port,
181
+ s->tx_pin, s->rx_pin, esp_err_to_name(err));
182
+
183
+ /* RX buffer only if we have an RX pin; no TX buffer (writes block until done) */
184
+ int rx_buf = s->rx_pin >= 0 ? MIK_UART_RX_BUF_SIZE : 0;
185
+ err = uart_driver_install(s->port, rx_buf, 0, 0, nullptr, ESP_INTR_FLAG_IRAM);
186
+ if (err != ESP_OK)
187
+ return mik__result_err_named(ctx, "DriverInstallFailed",
188
+ "uart_driver_install failed on port %d: %s", s->port,
189
+ esp_err_to_name(err));
190
+
191
+ s->begun = true;
192
+ mik__uart_track(ctx, s);
193
+ return mik__result_ok_void(ctx);
194
+ }
195
+
196
+ static JSValue js_uart_end(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
197
+ auto* s = mik__uart_get(ctx, this_val);
198
+ if (!s) return JS_EXCEPTION;
199
+ if (!s->begun) return mik__result_ok_void(ctx); // idempotent
200
+
201
+ /* Cancel any pending read. MIK_FreePromise frees the JSValues but
202
+ * doesn't reset the slot, so MIK_ClearPromise is required to keep
203
+ * MIK_IsPromisePending honest if the Uart is begun + read again. */
204
+ if (s->reading) {
205
+ if (MIK_IsPromisePending(ctx, &s->read_promise)) {
206
+ MIK_FreePromise(ctx, &s->read_promise);
207
+ MIK_ClearPromise(ctx, &s->read_promise);
208
+ }
209
+ s->reading = false;
210
+ }
211
+
212
+ mik__uart_untrack(ctx, s);
213
+ uart_driver_delete(s->port);
214
+ s->begun = false;
215
+ return mik__result_ok_void(ctx);
216
+ }
217
+
218
+ static JSValue js_uart_write(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
219
+ auto* s = mik__uart_get(ctx, this_val);
220
+ if (!s) return JS_EXCEPTION;
221
+ if (!s->begun) return mik__result_err_tag(ctx, "NotStarted");
222
+ if (s->tx_pin < 0) return mik__result_err_tag(ctx, "NoTxPin");
223
+
224
+ size_t data_len;
225
+ uint8_t* data;
226
+ size_t offset, elem_size;
227
+ JSValue ab = JS_GetTypedArrayBuffer(ctx, argv[0], &offset, &data_len, &elem_size);
228
+ if (!JS_IsException(ab)) {
229
+ data = JS_GetArrayBuffer(ctx, &data_len, ab);
230
+ JS_FreeValue(ctx, ab);
231
+ if (!data) return JS_ThrowTypeError(ctx, "expected Uint8Array as argument 1");
232
+ data += offset;
233
+ } else {
234
+ JSValue exc = JS_GetException(ctx);
235
+ JS_FreeValue(ctx, exc);
236
+ data = JS_GetArrayBuffer(ctx, &data_len, argv[0]);
237
+ if (!data) return JS_ThrowTypeError(ctx, "expected Uint8Array as argument 1");
238
+ }
239
+
240
+ int written = uart_write_bytes(s->port, data, data_len);
241
+ if (written < 0)
242
+ return mik__result_err_named(ctx, "WriteFailed",
243
+ "uart_write_bytes failed on port %d", s->port);
244
+
245
+ return mik__result_ok_void(ctx);
246
+ }
247
+
248
+ /* ── Async iterator for read() ────────────────────────────────────── */
249
+
250
+ /* The iterator object holds a reference back to the Uart state.
251
+ * next() either returns buffered data synchronously or creates a promise.
252
+ * return() cancels the pending read. */
253
+
254
+ static JSClassID mik_uart_iter_class_id;
255
+
256
+ struct MIKUartIterState {
257
+ /* Borrowed pointer into the Uart instance's opaque state. Kept alive
258
+ * for the iterator's lifetime by `uart_jsval` below — without that
259
+ * strong ref, QuickJS finalization order isn't guaranteed and the
260
+ * iterator could outlive its parent Uart, leading to a UAF here. */
261
+ MIKUartState* uart;
262
+ JSValue uart_jsval;
263
+ };
264
+
265
+ static void mik__uart_iter_finalizer(JSRuntime* rt, JSValue val) {
266
+ auto* it = static_cast<MIKUartIterState*>(JS_GetOpaque(val, mik_uart_iter_class_id));
267
+ if (!it) return;
268
+ /* uart_jsval keeps the Uart alive, so accessing it->uart->reading is
269
+ * safe here — except after iterator.return() nulled it out. */
270
+ if (it->uart) {
271
+ it->uart->reading = false;
272
+ }
273
+ JS_FreeValueRT(rt, it->uart_jsval);
274
+ free(it);
275
+ }
276
+
277
+ static void mik__uart_iter_gc_mark(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) {
278
+ auto* it = static_cast<MIKUartIterState*>(JS_GetOpaque(val, mik_uart_iter_class_id));
279
+ if (!it) return;
280
+ JS_MarkValue(rt, it->uart_jsval, mark_func);
281
+ }
282
+
283
+ static JSClassDef mik_uart_iter_class = {
284
+ .class_name = "UartIterator",
285
+ .finalizer = mik__uart_iter_finalizer,
286
+ .gc_mark = mik__uart_iter_gc_mark,
287
+ };
288
+
289
+ /* Try to read available data and return {done: false, value: Uint8Array} synchronously */
290
+ static JSValue mik__uart_try_read(JSContext* ctx, MIKUartState* s) {
291
+ size_t buffered = 0;
292
+ uart_get_buffered_data_len(s->port, &buffered);
293
+ if (buffered == 0) return JS_UNDEFINED; // sentinel: no data
294
+
295
+ auto* buf = static_cast<uint8_t*>(js_malloc(ctx, buffered));
296
+ if (!buf) return JS_EXCEPTION;
297
+
298
+ int read = uart_read_bytes(s->port, buf, buffered, 0);
299
+ if (read <= 0) {
300
+ js_free(ctx, buf);
301
+ return JS_UNDEFINED;
302
+ }
303
+
304
+ JSValue arr = MIK_NewUint8Array(ctx, buf, read);
305
+ JSValue result = JS_NewObject(ctx);
306
+ JS_DefinePropertyValueStr(ctx, result, "done", JS_FALSE, JS_PROP_C_W_E);
307
+ JS_DefinePropertyValueStr(ctx, result, "value", arr, JS_PROP_C_W_E);
308
+ return result;
309
+ }
310
+
311
+ static JSValue js_uart_iter_next(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
312
+ auto* it = static_cast<MIKUartIterState*>(JS_GetOpaque2(ctx, this_val, mik_uart_iter_class_id));
313
+ if (!it || !it->uart) return JS_ThrowInternalError(ctx, "invalid uart iterator");
314
+ MIKUartState* s = it->uart;
315
+
316
+ if (!s->begun) return JS_Throw(ctx, JS_NewString(ctx, "UART not started"));
317
+
318
+ /* Try synchronous read first */
319
+ JSValue sync_result = mik__uart_try_read(ctx, s);
320
+ if (JS_IsException(sync_result)) return sync_result;
321
+ if (!JS_IsUndefined(sync_result)) {
322
+ /* Async iterator protocol requires next() to return a Promise */
323
+ return MIK_NewResolvedPromise(ctx, 1, &sync_result);
324
+ }
325
+
326
+ /* No data available: create a promise, resolved by loop consumer */
327
+ JSValue promise = MIK_InitPromise(ctx, &s->read_promise);
328
+ return promise;
329
+ }
330
+
331
+ static JSValue js_uart_iter_return(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
332
+ auto* it = static_cast<MIKUartIterState*>(JS_GetOpaque2(ctx, this_val, mik_uart_iter_class_id));
333
+ if (!it || !it->uart) goto done;
334
+
335
+ /* Cancel pending read promise */
336
+ if (MIK_IsPromisePending(ctx, &it->uart->read_promise)) {
337
+ /* Resolve with {done: true} to cleanly end the iteration */
338
+ JSValue done_result = JS_NewObject(ctx);
339
+ JS_DefinePropertyValueStr(ctx, done_result, "done", JS_TRUE, JS_PROP_C_W_E);
340
+ JS_DefinePropertyValueStr(ctx, done_result, "value", JS_UNDEFINED, JS_PROP_C_W_E);
341
+ MIK_ResolvePromise(ctx, &it->uart->read_promise, 1, &done_result);
342
+ MIK_ClearPromise(ctx, &it->uart->read_promise);
343
+ }
344
+
345
+ it->uart->reading = false;
346
+ it->uart = nullptr;
347
+
348
+ done:
349
+ JSValue result = JS_NewObject(ctx);
350
+ JS_DefinePropertyValueStr(ctx, result, "done", JS_TRUE, JS_PROP_C_W_E);
351
+ JS_DefinePropertyValueStr(ctx, result, "value", JS_UNDEFINED, JS_PROP_C_W_E);
352
+ return MIK_NewResolvedPromise(ctx, 1, &result);
353
+ }
354
+
355
+ static const JSCFunctionListEntry mik_uart_iter_proto_funcs[] = {
356
+ MIK_CFUNC_DEF("next", 0, js_uart_iter_next),
357
+ MIK_CFUNC_DEF("return", 0, js_uart_iter_return),
358
+ };
359
+
360
+ /* read() method on the Uart class — returns Result<AsyncIterable, UartError> */
361
+ static JSValue js_uart_read(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
362
+ auto* s = mik__uart_get(ctx, this_val);
363
+ if (!s) return JS_EXCEPTION;
364
+ if (!s->begun) return mik__result_err_tag(ctx, "NotStarted");
365
+ if (s->rx_pin < 0) return mik__result_err_tag(ctx, "NoRxPin");
366
+ if (s->reading) return mik__result_err_tag(ctx, "AlreadyReading");
367
+
368
+ s->reading = true;
369
+
370
+ /* Create iterator object */
371
+ auto* it = static_cast<MIKUartIterState*>(calloc(1, sizeof(MIKUartIterState)));
372
+ if (!it) {
373
+ s->reading = false;
374
+ return JS_ThrowOutOfMemory(ctx);
375
+ }
376
+ it->uart = s;
377
+ it->uart_jsval = JS_DupValue(ctx, this_val);
378
+
379
+ JSValue iter_obj = JS_NewObjectClass(ctx, mik_uart_iter_class_id);
380
+ if (JS_IsException(iter_obj)) {
381
+ s->reading = false;
382
+ JS_FreeValue(ctx, it->uart_jsval);
383
+ free(it);
384
+ return JS_EXCEPTION;
385
+ }
386
+ JS_SetOpaque(iter_obj, it);
387
+
388
+ return mik__result_ok(ctx, iter_obj);
389
+ }
390
+
391
+ /* ── Prototype ────────────────────────────────────────────────────── */
392
+
393
+ static const JSCFunctionListEntry mik_uart_proto_funcs[] = {
394
+ MIK_CFUNC_DEF("begin", 0, js_uart_begin),
395
+ MIK_CFUNC_DEF("end", 0, js_uart_end),
396
+ MIK_CFUNC_DEF("write", 1, js_uart_write),
397
+ MIK_CFUNC_DEF("read", 0, js_uart_read),
398
+ };
399
+
400
+ /* ── Module init ──────────────────────────────────────────────────── */
401
+
402
+ static int mik__uart_module_init(JSContext* ctx, JSModuleDef* m) {
403
+ JSValue ctor =
404
+ JS_NewCFunction2(ctx, js_uart_constructor, "Uart", 2, JS_CFUNC_constructor, 0);
405
+ JS_SetModuleExport(ctx, m, "Uart", ctor);
406
+ return 0;
407
+ }
408
+
409
+ static JSModuleDef* mik__uart_init(JSContext* ctx) {
410
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
411
+ mik__uart_slot = MIK_AllocModuleSlot(mik_rt);
412
+
413
+ /* Allocate per-runtime slot data */
414
+ auto* slot_data = static_cast<MIKUartSlot*>(calloc(1, sizeof(MIKUartSlot)));
415
+ if (!slot_data) return nullptr;
416
+ mik__uart_slot_data(mik_rt) = slot_data;
417
+
418
+ JSRuntime* rt = JS_GetRuntime(ctx);
419
+
420
+ /* Register Uart class */
421
+ JS_NewClassID(rt, &mik_uart_class_id);
422
+ JS_NewClass(rt, mik_uart_class_id, &mik_uart_class);
423
+
424
+ JSValue proto = JS_NewObject(ctx);
425
+ JS_SetPropertyFunctionList(ctx, proto, mik_uart_proto_funcs, countof(mik_uart_proto_funcs));
426
+ JS_SetClassProto(ctx, mik_uart_class_id, proto);
427
+
428
+ /* Register iterator class */
429
+ JS_NewClassID(rt, &mik_uart_iter_class_id);
430
+ JS_NewClass(rt, mik_uart_iter_class_id, &mik_uart_iter_class);
431
+
432
+ JSValue iter_proto = JS_NewObject(ctx);
433
+ JS_SetPropertyFunctionList(ctx, iter_proto, mik_uart_iter_proto_funcs,
434
+ countof(mik_uart_iter_proto_funcs));
435
+ JS_SetClassProto(ctx, mik_uart_iter_class_id, iter_proto);
436
+
437
+ /* Register module */
438
+ JSModuleDef* m = JS_NewCModule(ctx, "native:uart", mik__uart_module_init);
439
+ if (!m) return nullptr;
440
+ JS_AddModuleExport(ctx, m, "Uart");
441
+ return m;
442
+ }
443
+
444
+ /* ── Event loop: check for incoming UART data ─────────────────────── */
445
+
446
+ void mik__uart_consume(JSContext* ctx) {
447
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
448
+ CHECK_NOT_NULL(mik_rt);
449
+ auto* slot = mik__uart_slot_data(mik_rt);
450
+ if (!slot) return;
451
+
452
+ for (int i = 0; i < slot->count; i++) {
453
+ MIKUartState* s = slot->instances[i];
454
+ if (!s || !s->reading || !s->begun) continue;
455
+ if (!MIK_IsPromisePending(ctx, &s->read_promise)) continue;
456
+
457
+ size_t buffered = 0;
458
+ uart_get_buffered_data_len(s->port, &buffered);
459
+ if (buffered == 0) continue;
460
+
461
+ auto* buf = static_cast<uint8_t*>(js_malloc(ctx, buffered));
462
+ if (!buf) continue;
463
+
464
+ int read_bytes = uart_read_bytes(s->port, buf, buffered, 0);
465
+ if (read_bytes <= 0) {
466
+ js_free(ctx, buf);
467
+ continue;
468
+ }
469
+
470
+ JSValue arr = MIK_NewUint8Array(ctx, buf, read_bytes);
471
+ JSValue result = JS_NewObject(ctx);
472
+ JS_DefinePropertyValueStr(ctx, result, "done", JS_FALSE, JS_PROP_C_W_E);
473
+ JS_DefinePropertyValueStr(ctx, result, "value", arr, JS_PROP_C_W_E);
474
+ MIK_ResolvePromise(ctx, &s->read_promise, 1, &result);
475
+ MIK_ClearPromise(ctx, &s->read_promise);
476
+ }
477
+ }
478
+
479
+ void mik__uart_destroy(JSContext* ctx) {
480
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
481
+ CHECK_NOT_NULL(mik_rt);
482
+ auto* slot = mik__uart_slot_data(mik_rt);
483
+ if (!slot) return;
484
+
485
+ for (int i = 0; i < slot->count; i++) {
486
+ MIKUartState* s = slot->instances[i];
487
+ if (!s) continue;
488
+ if (s->reading && MIK_IsPromisePending(ctx, &s->read_promise)) {
489
+ MIK_FreePromise(ctx, &s->read_promise);
490
+ }
491
+ }
492
+
493
+ free(slot);
494
+ mik__uart_slot_data(mik_rt) = nullptr;
495
+ }
496
+
497
+ MIK_REGISTER_MODULE(uart, "native:uart", mik__uart_init, mik__uart_consume, mik__uart_destroy)