@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,437 @@
1
+ #include <cstddef>
2
+ #include <cstring>
3
+
4
+ #include "driver/rmt_encoder.h"
5
+ #include "driver/rmt_tx.h"
6
+ #include "freertos/FreeRTOS.h"
7
+ #include "esp_log.h"
8
+ #include "mikrojs/mikrojs.h"
9
+ #include "mikrojs/private.h"
10
+ #include "mikrojs/utils.h"
11
+
12
+ #define MIK_NEOPIXEL_TAG "native:neopixel"
13
+ #define MIK_NEOPIXEL_RMT_RESOLUTION_HZ 10000000 // 10 MHz → 0.1 µs per tick
14
+ #define MIK_NEOPIXEL_MAX_LEDS 1024
15
+
16
+ static JSClassID mik_neopixel_class_id;
17
+
18
+ /* ── WS2812 RMT encoder (based on ESP-IDF led_strip example) ───────── */
19
+
20
+ struct mik_led_strip_encoder_t {
21
+ rmt_encoder_t base;
22
+ rmt_encoder_t* bytes_encoder;
23
+ rmt_encoder_t* copy_encoder;
24
+ int state;
25
+ rmt_symbol_word_t reset_code;
26
+ };
27
+
28
+ /* C++-safe container_of: recover enclosing struct from its `base` member */
29
+ static inline mik_led_strip_encoder_t* mik__encoder_from_base(rmt_encoder_t* base_ptr) {
30
+ return reinterpret_cast<mik_led_strip_encoder_t*>(
31
+ reinterpret_cast<char*>(base_ptr) - offsetof(mik_led_strip_encoder_t, base));
32
+ }
33
+
34
+ RMT_ENCODER_FUNC_ATTR
35
+ static size_t mik__neopixel_encode(rmt_encoder_t* encoder, rmt_channel_handle_t channel,
36
+ const void* primary_data, size_t data_size,
37
+ rmt_encode_state_t* ret_state) {
38
+ auto* led_enc = mik__encoder_from_base(encoder);
39
+ rmt_encoder_handle_t bytes_encoder = led_enc->bytes_encoder;
40
+ rmt_encoder_handle_t copy_encoder = led_enc->copy_encoder;
41
+ rmt_encode_state_t session_state = RMT_ENCODING_RESET;
42
+ int state = 0;
43
+ size_t encoded_symbols = 0;
44
+
45
+ switch (led_enc->state) {
46
+ case 0: // send pixel data
47
+ encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data,
48
+ data_size, &session_state);
49
+ if (session_state & RMT_ENCODING_COMPLETE) {
50
+ led_enc->state = 1;
51
+ }
52
+ if (session_state & RMT_ENCODING_MEM_FULL) {
53
+ state |= RMT_ENCODING_MEM_FULL;
54
+ goto out;
55
+ }
56
+ // fall-through
57
+ case 1: // send reset code
58
+ encoded_symbols +=
59
+ copy_encoder->encode(copy_encoder, channel, &led_enc->reset_code,
60
+ sizeof(led_enc->reset_code), &session_state);
61
+ if (session_state & RMT_ENCODING_COMPLETE) {
62
+ led_enc->state = RMT_ENCODING_RESET;
63
+ state |= RMT_ENCODING_COMPLETE;
64
+ }
65
+ if (session_state & RMT_ENCODING_MEM_FULL) {
66
+ state |= RMT_ENCODING_MEM_FULL;
67
+ goto out;
68
+ }
69
+ }
70
+ out:
71
+ *ret_state = static_cast<rmt_encode_state_t>(state);
72
+ return encoded_symbols;
73
+ }
74
+
75
+ static esp_err_t mik__neopixel_encoder_del(rmt_encoder_t* encoder) {
76
+ auto* led_enc = mik__encoder_from_base(encoder);
77
+ rmt_del_encoder(led_enc->bytes_encoder);
78
+ rmt_del_encoder(led_enc->copy_encoder);
79
+ free(led_enc);
80
+ return ESP_OK;
81
+ }
82
+
83
+ RMT_ENCODER_FUNC_ATTR
84
+ static esp_err_t mik__neopixel_encoder_reset(rmt_encoder_t* encoder) {
85
+ auto* led_enc = mik__encoder_from_base(encoder);
86
+ rmt_encoder_reset(led_enc->bytes_encoder);
87
+ rmt_encoder_reset(led_enc->copy_encoder);
88
+ led_enc->state = RMT_ENCODING_RESET;
89
+ return ESP_OK;
90
+ }
91
+
92
+ static esp_err_t mik__neopixel_new_encoder(uint32_t resolution,
93
+ rmt_encoder_handle_t* ret_encoder) {
94
+ auto* led_enc = static_cast<mik_led_strip_encoder_t*>(
95
+ rmt_alloc_encoder_mem(sizeof(mik_led_strip_encoder_t)));
96
+ if (!led_enc) return ESP_ERR_NO_MEM;
97
+
98
+ led_enc->base.encode = mik__neopixel_encode;
99
+ led_enc->base.del = mik__neopixel_encoder_del;
100
+ led_enc->base.reset = mik__neopixel_encoder_reset;
101
+
102
+ // WS2812 timing: T0H=0.3µs T0L=0.9µs T1H=0.9µs T1L=0.3µs
103
+ rmt_bytes_encoder_config_t bytes_cfg = {};
104
+ bytes_cfg.bit0.level0 = 1;
105
+ bytes_cfg.bit0.duration0 = 0.3 * resolution / 1000000; // T0H
106
+ bytes_cfg.bit0.level1 = 0;
107
+ bytes_cfg.bit0.duration1 = 0.9 * resolution / 1000000; // T0L
108
+ bytes_cfg.bit1.level0 = 1;
109
+ bytes_cfg.bit1.duration0 = 0.9 * resolution / 1000000; // T1H
110
+ bytes_cfg.bit1.level1 = 0;
111
+ bytes_cfg.bit1.duration1 = 0.3 * resolution / 1000000; // T1L
112
+ bytes_cfg.flags.msb_first = 1; // WS2812: G7..G0 R7..R0 B7..B0
113
+
114
+ esp_err_t err = rmt_new_bytes_encoder(&bytes_cfg, &led_enc->bytes_encoder);
115
+ if (err != ESP_OK) {
116
+ free(led_enc);
117
+ return err;
118
+ }
119
+
120
+ rmt_copy_encoder_config_t copy_cfg = {};
121
+ err = rmt_new_copy_encoder(&copy_cfg, &led_enc->copy_encoder);
122
+ if (err != ESP_OK) {
123
+ rmt_del_encoder(led_enc->bytes_encoder);
124
+ free(led_enc);
125
+ return err;
126
+ }
127
+
128
+ // Reset code ≥ 50 µs low
129
+ uint32_t reset_ticks = resolution / 1000000 * 50 / 2;
130
+ led_enc->reset_code = {};
131
+ led_enc->reset_code.level0 = 0;
132
+ led_enc->reset_code.duration0 = reset_ticks;
133
+ led_enc->reset_code.level1 = 0;
134
+ led_enc->reset_code.duration1 = reset_ticks;
135
+
136
+ *ret_encoder = &led_enc->base;
137
+ return ESP_OK;
138
+ }
139
+
140
+ /* ── Per-instance state ────────────────────────────────────────────── */
141
+
142
+ typedef struct {
143
+ int gpio;
144
+ int num_leds;
145
+ int bytes_per_led; // 3 for RGB (GRB wire order), 4 for RGBW (GRBW)
146
+ rmt_channel_handle_t channel;
147
+ rmt_encoder_handle_t encoder;
148
+ uint8_t* pixel_buf; // GRB(W) encoded, num_leds * bytes_per_led
149
+ bool active;
150
+ } MIKNeoPixelState;
151
+
152
+ /* ── Helpers ───────────────────────────────────────────────────────── */
153
+
154
+ static MIKNeoPixelState* mik__neopixel_get(JSContext* ctx, JSValue this_val) {
155
+ return static_cast<MIKNeoPixelState*>(JS_GetOpaque2(ctx, this_val, mik_neopixel_class_id));
156
+ }
157
+
158
+ /* ── Finalizer ─────────────────────────────────────────────────────── */
159
+
160
+ static void mik__neopixel_finalizer(JSRuntime* rt, JSValue val) {
161
+ auto* s = static_cast<MIKNeoPixelState*>(JS_GetOpaque(val, mik_neopixel_class_id));
162
+ if (!s) return;
163
+ if (s->active) {
164
+ rmt_disable(s->channel);
165
+ rmt_del_encoder(s->encoder);
166
+ rmt_del_channel(s->channel);
167
+ }
168
+ free(s->pixel_buf);
169
+ free(s);
170
+ }
171
+
172
+ static JSClassDef mik_neopixel_class = {
173
+ .class_name = "NeoPixel",
174
+ .finalizer = mik__neopixel_finalizer,
175
+ };
176
+
177
+ /* ── Constructor ───────────────────────────────────────────────────── */
178
+
179
+ static JSValue js_neopixel_constructor(JSContext* ctx, JSValue new_target, int argc,
180
+ JSValue* argv) {
181
+ if (argc < 2)
182
+ return JS_ThrowTypeError(ctx,
183
+ "NeoPixel requires (pin, numLeds) or (pin, numLeds, bytesPerLed)");
184
+
185
+ int32_t gpio;
186
+ if (JS_ToInt32(ctx, &gpio, argv[0])) return JS_EXCEPTION;
187
+
188
+ int32_t num_leds;
189
+ if (JS_ToInt32(ctx, &num_leds, argv[1])) return JS_EXCEPTION;
190
+ if (num_leds <= 0 || num_leds > MIK_NEOPIXEL_MAX_LEDS)
191
+ return JS_ThrowRangeError(ctx, "numLeds must be between 1 and %d", MIK_NEOPIXEL_MAX_LEDS);
192
+
193
+ int32_t bytes_per_led = 3; // default: RGB (GRB on wire)
194
+ if (argc >= 3) {
195
+ if (JS_ToInt32(ctx, &bytes_per_led, argv[2])) return JS_EXCEPTION;
196
+ if (bytes_per_led != 3 && bytes_per_led != 4)
197
+ return JS_ThrowRangeError(ctx, "bytesPerLed must be 3 (RGB) or 4 (RGBW)");
198
+ }
199
+
200
+ /* Allocate pixel buffer */
201
+ size_t buf_size = static_cast<size_t>(num_leds) * bytes_per_led;
202
+ auto* pixel_buf = static_cast<uint8_t*>(calloc(1, buf_size));
203
+ if (!pixel_buf) return JS_ThrowOutOfMemory(ctx);
204
+
205
+ /* Create RMT TX channel */
206
+ rmt_channel_handle_t channel = nullptr;
207
+ rmt_tx_channel_config_t tx_cfg = {};
208
+ tx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
209
+ tx_cfg.gpio_num = static_cast<gpio_num_t>(gpio);
210
+ tx_cfg.mem_block_symbols = 64;
211
+ tx_cfg.resolution_hz = MIK_NEOPIXEL_RMT_RESOLUTION_HZ;
212
+ tx_cfg.trans_queue_depth = 4;
213
+
214
+ esp_err_t err = rmt_new_tx_channel(&tx_cfg, &channel);
215
+ if (err != ESP_OK) {
216
+ free(pixel_buf);
217
+ return JS_ThrowInternalError(ctx, "RMT channel create failed: %s", esp_err_to_name(err));
218
+ }
219
+
220
+ /* Create LED strip encoder */
221
+ rmt_encoder_handle_t encoder = nullptr;
222
+ err = mik__neopixel_new_encoder(MIK_NEOPIXEL_RMT_RESOLUTION_HZ, &encoder);
223
+ if (err != ESP_OK) {
224
+ rmt_del_channel(channel);
225
+ free(pixel_buf);
226
+ return JS_ThrowInternalError(ctx, "LED encoder create failed: %s", esp_err_to_name(err));
227
+ }
228
+
229
+ /* Enable channel */
230
+ err = rmt_enable(channel);
231
+ if (err != ESP_OK) {
232
+ rmt_del_encoder(encoder);
233
+ rmt_del_channel(channel);
234
+ free(pixel_buf);
235
+ return JS_ThrowInternalError(ctx, "RMT enable failed: %s", esp_err_to_name(err));
236
+ }
237
+
238
+ /* Allocate state */
239
+ auto* s = static_cast<MIKNeoPixelState*>(calloc(1, sizeof(MIKNeoPixelState)));
240
+ if (!s) {
241
+ rmt_disable(channel);
242
+ rmt_del_encoder(encoder);
243
+ rmt_del_channel(channel);
244
+ free(pixel_buf);
245
+ return JS_ThrowOutOfMemory(ctx);
246
+ }
247
+ s->gpio = gpio;
248
+ s->num_leds = num_leds;
249
+ s->bytes_per_led = bytes_per_led;
250
+ s->channel = channel;
251
+ s->encoder = encoder;
252
+ s->pixel_buf = pixel_buf;
253
+ s->active = true;
254
+
255
+ JSValue obj = JS_NewObjectClass(ctx, mik_neopixel_class_id);
256
+ if (JS_IsException(obj)) {
257
+ rmt_disable(channel);
258
+ rmt_del_encoder(encoder);
259
+ rmt_del_channel(channel);
260
+ free(pixel_buf);
261
+ free(s);
262
+ return obj;
263
+ }
264
+ JS_SetOpaque(obj, s);
265
+ return obj;
266
+ }
267
+
268
+ /* ── Methods ───────────────────────────────────────────────────────── */
269
+
270
+ /* setPixel(index, r, g, b [, w]) */
271
+ static JSValue js_neopixel_set_pixel(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
272
+ auto* s = mik__neopixel_get(ctx, this_val);
273
+ if (!s) return JS_EXCEPTION;
274
+ if (!s->active)
275
+ return mik__result_err_tag(ctx, "NotActive");
276
+
277
+ int32_t index;
278
+ if (JS_ToInt32(ctx, &index, argv[0])) return JS_EXCEPTION;
279
+ if (index < 0 || index >= s->num_leds) return mik__result_err_tag(ctx, "IndexOutOfRange");
280
+
281
+ int32_t r, g, b;
282
+ if (JS_ToInt32(ctx, &r, argv[1])) return JS_EXCEPTION;
283
+ if (JS_ToInt32(ctx, &g, argv[2])) return JS_EXCEPTION;
284
+ if (JS_ToInt32(ctx, &b, argv[3])) return JS_EXCEPTION;
285
+
286
+ uint8_t* p = s->pixel_buf + index * s->bytes_per_led;
287
+ // WS2812 wire order: GRB
288
+ p[0] = static_cast<uint8_t>(g & 0xFF);
289
+ p[1] = static_cast<uint8_t>(r & 0xFF);
290
+ p[2] = static_cast<uint8_t>(b & 0xFF);
291
+
292
+ if (s->bytes_per_led == 4) {
293
+ int32_t w = 0;
294
+ if (argc >= 5) {
295
+ if (JS_ToInt32(ctx, &w, argv[4])) return JS_EXCEPTION;
296
+ }
297
+ p[3] = static_cast<uint8_t>(w & 0xFF);
298
+ }
299
+
300
+ return mik__result_ok_void(ctx);
301
+ }
302
+
303
+ /* fill(r, g, b [, w]) */
304
+ static JSValue js_neopixel_fill(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
305
+ auto* s = mik__neopixel_get(ctx, this_val);
306
+ if (!s) return JS_EXCEPTION;
307
+ if (!s->active)
308
+ return mik__result_err_tag(ctx, "NotActive");
309
+
310
+ int32_t r, g, b;
311
+ if (JS_ToInt32(ctx, &r, argv[0])) return JS_EXCEPTION;
312
+ if (JS_ToInt32(ctx, &g, argv[1])) return JS_EXCEPTION;
313
+ if (JS_ToInt32(ctx, &b, argv[2])) return JS_EXCEPTION;
314
+
315
+ int32_t w = 0;
316
+ if (s->bytes_per_led == 4 && argc >= 4) {
317
+ if (JS_ToInt32(ctx, &w, argv[3])) return JS_EXCEPTION;
318
+ }
319
+
320
+ for (int i = 0; i < s->num_leds; i++) {
321
+ uint8_t* p = s->pixel_buf + i * s->bytes_per_led;
322
+ p[0] = static_cast<uint8_t>(g & 0xFF);
323
+ p[1] = static_cast<uint8_t>(r & 0xFF);
324
+ p[2] = static_cast<uint8_t>(b & 0xFF);
325
+ if (s->bytes_per_led == 4) {
326
+ p[3] = static_cast<uint8_t>(w & 0xFF);
327
+ }
328
+ }
329
+
330
+ return mik__result_ok_void(ctx);
331
+ }
332
+
333
+ /* show() — transmit pixel buffer via RMT */
334
+ static JSValue js_neopixel_show(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
335
+ auto* s = mik__neopixel_get(ctx, this_val);
336
+ if (!s) return JS_EXCEPTION;
337
+ if (!s->active)
338
+ return mik__result_err_tag(ctx, "NotActive");
339
+
340
+ rmt_transmit_config_t tx_cfg = {};
341
+ tx_cfg.loop_count = 0;
342
+
343
+ size_t buf_size = static_cast<size_t>(s->num_leds) * s->bytes_per_led;
344
+ esp_err_t err = rmt_transmit(s->channel, s->encoder, s->pixel_buf, buf_size, &tx_cfg);
345
+ if (err != ESP_OK)
346
+ return mik__result_err_named(ctx, "ShowFailed", "RMT transmit failed: %s",
347
+ esp_err_to_name(err));
348
+
349
+ err = rmt_tx_wait_all_done(s->channel, pdMS_TO_TICKS(1000));
350
+ if (err != ESP_OK)
351
+ return mik__result_err_named(ctx, "ShowFailed",
352
+ "RMT wait for transmit failed: %s", esp_err_to_name(err));
353
+
354
+ return mik__result_ok_void(ctx);
355
+ }
356
+
357
+ /* clear() — zero all pixels and transmit */
358
+ static JSValue js_neopixel_clear(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
359
+ auto* s = mik__neopixel_get(ctx, this_val);
360
+ if (!s) return JS_EXCEPTION;
361
+ if (!s->active)
362
+ return mik__result_err_tag(ctx, "NotActive");
363
+
364
+ memset(s->pixel_buf, 0, static_cast<size_t>(s->num_leds) * s->bytes_per_led);
365
+
366
+ rmt_transmit_config_t tx_cfg = {};
367
+ tx_cfg.loop_count = 0;
368
+
369
+ size_t buf_size = static_cast<size_t>(s->num_leds) * s->bytes_per_led;
370
+ esp_err_t err = rmt_transmit(s->channel, s->encoder, s->pixel_buf, buf_size, &tx_cfg);
371
+ if (err != ESP_OK)
372
+ return mik__result_err_named(ctx, "ShowFailed", "RMT transmit failed: %s",
373
+ esp_err_to_name(err));
374
+
375
+ err = rmt_tx_wait_all_done(s->channel, pdMS_TO_TICKS(1000));
376
+ if (err != ESP_OK)
377
+ return mik__result_err_named(ctx, "ShowFailed",
378
+ "RMT wait for transmit failed: %s", esp_err_to_name(err));
379
+
380
+ return mik__result_ok_void(ctx);
381
+ }
382
+
383
+ /* end() — release RMT resources */
384
+ static JSValue js_neopixel_end(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
385
+ auto* s = mik__neopixel_get(ctx, this_val);
386
+ if (!s) return JS_EXCEPTION;
387
+ if (!s->active) return mik__result_ok_void(ctx); // idempotent
388
+
389
+ rmt_disable(s->channel);
390
+ rmt_del_encoder(s->encoder);
391
+ rmt_del_channel(s->channel);
392
+ s->encoder = nullptr;
393
+ s->channel = nullptr;
394
+ s->active = false;
395
+ return mik__result_ok_void(ctx);
396
+ }
397
+
398
+ /* ── Prototype ─────────────────────────────────────────────────────── */
399
+
400
+ static const JSCFunctionListEntry mik_neopixel_proto_funcs[] = {
401
+ MIK_CFUNC_DEF("setPixel", 5, js_neopixel_set_pixel),
402
+ MIK_CFUNC_DEF("fill", 4, js_neopixel_fill),
403
+ MIK_CFUNC_DEF("show", 0, js_neopixel_show),
404
+ MIK_CFUNC_DEF("clear", 0, js_neopixel_clear),
405
+ MIK_CFUNC_DEF("end", 0, js_neopixel_end),
406
+ };
407
+
408
+ /* ── Module init ───────────────────────────────────────────────────── */
409
+
410
+ static int mik__neopixel_module_init(JSContext* ctx, JSModuleDef* m) {
411
+ JSValue ctor = JS_NewCFunction2(ctx, js_neopixel_constructor, "NeoPixel", 3,
412
+ JS_CFUNC_constructor, 0);
413
+ JS_SetModuleExport(ctx, m, "NeoPixel", ctor);
414
+ return 0;
415
+ }
416
+
417
+ static JSModuleDef* mik__neopixel_init(JSContext* ctx) {
418
+ JSRuntime* rt = JS_GetRuntime(ctx);
419
+
420
+ /* Register class (once per runtime) */
421
+ JS_NewClassID(rt, &mik_neopixel_class_id);
422
+ JS_NewClass(rt, mik_neopixel_class_id, &mik_neopixel_class);
423
+
424
+ /* Create prototype with methods */
425
+ JSValue proto = JS_NewObject(ctx);
426
+ JS_SetPropertyFunctionList(ctx, proto, mik_neopixel_proto_funcs,
427
+ countof(mik_neopixel_proto_funcs));
428
+ JS_SetClassProto(ctx, mik_neopixel_class_id, proto); /* consumed */
429
+
430
+ /* Register module */
431
+ JSModuleDef* m = JS_NewCModule(ctx, "native:neopixel", mik__neopixel_module_init);
432
+ if (!m) return nullptr;
433
+ JS_AddModuleExport(ctx, m, "NeoPixel");
434
+ return m;
435
+ }
436
+
437
+ MIK_REGISTER_MODULE(neopixel, "native:neopixel", mik__neopixel_init, nullptr, nullptr)
@@ -0,0 +1,219 @@
1
+ /**
2
+ * native:nvs_kv — NVS-backed key-value storage native module.
3
+ *
4
+ * Values are CBOR-encoded and stored as NVS blobs in the "mik.kv" namespace.
5
+ * Survives power cycles. NVS keys are limited to 15 characters.
6
+ */
7
+
8
+ #include <cstring>
9
+
10
+ #include <nanocbor/nanocbor.h>
11
+ #include <nvs.h>
12
+
13
+ #include "mikrojs/cbor_helpers.h"
14
+ #include "mikrojs/errors.h"
15
+ #include "mikrojs/private.h"
16
+ #include "mikrojs/utils.h"
17
+
18
+ #define MIK_NVS_KV_NS "mik.kv"
19
+ #define MIK_NVS_MAX_KEY_LEN 15
20
+ #define MIK_NVS_MAX_VAL_LEN (16 * 1024)
21
+
22
+ /* ── JS functions ────────────────────────────────────────────────── */
23
+
24
+ static JSValue mik__nvs_kv_set(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
25
+ const char* key = JS_ToCString(ctx, argv[0]);
26
+ if (!key) return JS_EXCEPTION;
27
+ size_t key_len = strlen(key);
28
+
29
+ if (key_len == 0 || key_len > MIK_NVS_MAX_KEY_LEN) {
30
+ JS_FreeCString(ctx, key);
31
+ return mik__result_err(ctx, MIK_ERR_KV_INVALID_KEY, 0,
32
+ "NVS key length must be 1-%d bytes", MIK_NVS_MAX_KEY_LEN);
33
+ }
34
+
35
+ /* CBOR-encode the value (two-pass: measure then encode) */
36
+ nanocbor_encoder_t enc;
37
+ nanocbor_encoder_init(&enc, nullptr, 0);
38
+ if (mik__cbor_encode_value(ctx, &enc, argv[1], 0) < 0) {
39
+ JS_FreeCString(ctx, key);
40
+ return mik__result_err(ctx, MIK_ERR_KV_ENCODE, 0, "NVS value is not CBOR-encodable");
41
+ }
42
+ size_t val_len = nanocbor_encoded_len(&enc);
43
+
44
+ if (val_len > MIK_NVS_MAX_VAL_LEN) {
45
+ JS_FreeCString(ctx, key);
46
+ return mik__result_err(ctx, MIK_ERR_KV_TOO_LARGE, 0,
47
+ "NVS value exceeds %d bytes", MIK_NVS_MAX_VAL_LEN);
48
+ }
49
+
50
+ /* Encode into buffer */
51
+ auto* buf = static_cast<uint8_t*>(js_malloc(ctx, val_len > 0 ? val_len : 1));
52
+ if (!buf) {
53
+ JS_FreeCString(ctx, key);
54
+ return JS_EXCEPTION;
55
+ }
56
+ nanocbor_encoder_init(&enc, buf, val_len);
57
+ mik__cbor_encode_value(ctx, &enc, argv[1], 0);
58
+
59
+ /* Write to NVS */
60
+ nvs_handle_t handle;
61
+ esp_err_t err = nvs_open(MIK_NVS_KV_NS, NVS_READWRITE, &handle);
62
+ if (err != ESP_OK) {
63
+ js_free(ctx, buf);
64
+ JS_FreeCString(ctx, key);
65
+ return mik__result_err(ctx, MIK_ERR_KV_WRITE, err,
66
+ "NVS open failed: %s", esp_err_to_name(err));
67
+ }
68
+
69
+ err = nvs_set_blob(handle, key, buf, val_len);
70
+ js_free(ctx, buf);
71
+ JS_FreeCString(ctx, key);
72
+
73
+ if (err != ESP_OK) {
74
+ nvs_close(handle);
75
+ return mik__result_err(ctx, MIK_ERR_KV_WRITE, err,
76
+ "NVS set failed: %s", esp_err_to_name(err));
77
+ }
78
+
79
+ err = nvs_commit(handle);
80
+ nvs_close(handle);
81
+
82
+ if (err != ESP_OK) {
83
+ return mik__result_err(ctx, MIK_ERR_KV_WRITE, err,
84
+ "NVS commit failed: %s", esp_err_to_name(err));
85
+ }
86
+
87
+ return mik__result_ok_void(ctx);
88
+ }
89
+
90
+ static JSValue mik__nvs_kv_get(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
91
+ const char* key = JS_ToCString(ctx, argv[0]);
92
+ if (!key) return JS_EXCEPTION;
93
+ size_t key_len = strlen(key);
94
+
95
+ if (key_len == 0 || key_len > MIK_NVS_MAX_KEY_LEN) {
96
+ JS_FreeCString(ctx, key);
97
+ return JS_UNDEFINED;
98
+ }
99
+
100
+ nvs_handle_t handle;
101
+ if (nvs_open(MIK_NVS_KV_NS, NVS_READONLY, &handle) != ESP_OK) {
102
+ JS_FreeCString(ctx, key);
103
+ return JS_UNDEFINED;
104
+ }
105
+
106
+ /* Query size first */
107
+ size_t len = 0;
108
+ esp_err_t err = nvs_get_blob(handle, key, nullptr, &len);
109
+ if (err != ESP_OK || len == 0) {
110
+ JS_FreeCString(ctx, key);
111
+ nvs_close(handle);
112
+ return JS_UNDEFINED;
113
+ }
114
+
115
+ /* Read blob */
116
+ auto* buf = static_cast<uint8_t*>(js_malloc(ctx, len));
117
+ if (!buf) {
118
+ JS_FreeCString(ctx, key);
119
+ nvs_close(handle);
120
+ return JS_EXCEPTION;
121
+ }
122
+
123
+ err = nvs_get_blob(handle, key, buf, &len);
124
+ JS_FreeCString(ctx, key);
125
+ nvs_close(handle);
126
+
127
+ if (err != ESP_OK) {
128
+ js_free(ctx, buf);
129
+ return JS_UNDEFINED;
130
+ }
131
+
132
+ /* Decode CBOR */
133
+ nanocbor_value_t decoder;
134
+ nanocbor_decoder_init(&decoder, buf, len);
135
+ JSValue result = mik__cbor_decode_value(ctx, &decoder, 0);
136
+ js_free(ctx, buf);
137
+
138
+ if (JS_IsException(result)) return JS_UNDEFINED;
139
+ return result;
140
+ }
141
+
142
+ static JSValue mik__nvs_kv_remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
143
+ const char* key = JS_ToCString(ctx, argv[0]);
144
+ if (!key) return JS_EXCEPTION;
145
+
146
+ nvs_handle_t handle;
147
+ if (nvs_open(MIK_NVS_KV_NS, NVS_READWRITE, &handle) != ESP_OK) {
148
+ JS_FreeCString(ctx, key);
149
+ return JS_NewBool(ctx, false);
150
+ }
151
+
152
+ esp_err_t err = nvs_erase_key(handle, key);
153
+ JS_FreeCString(ctx, key);
154
+
155
+ if (err != ESP_OK) {
156
+ nvs_close(handle);
157
+ return JS_NewBool(ctx, false);
158
+ }
159
+
160
+ nvs_commit(handle);
161
+ nvs_close(handle);
162
+ return JS_NewBool(ctx, true);
163
+ }
164
+
165
+ static JSValue mik__nvs_kv_clear(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
166
+ nvs_handle_t handle;
167
+ if (nvs_open(MIK_NVS_KV_NS, NVS_READWRITE, &handle) != ESP_OK) {
168
+ return JS_UNDEFINED;
169
+ }
170
+
171
+ nvs_erase_all(handle);
172
+ nvs_commit(handle);
173
+ nvs_close(handle);
174
+ return JS_UNDEFINED;
175
+ }
176
+
177
+ static JSValue mik__nvs_kv_info(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
178
+ /* Partition-level stats (shared across all namespaces) */
179
+ nvs_stats_t stats = {};
180
+ nvs_get_stats(nullptr, &stats);
181
+
182
+ nvs_handle_t handle;
183
+ size_t used_count = 0;
184
+ if (nvs_open(MIK_NVS_KV_NS, NVS_READONLY, &handle) == ESP_OK) {
185
+ nvs_get_used_entry_count(handle, &used_count);
186
+ nvs_close(handle);
187
+ }
188
+
189
+ JSValue obj = JS_NewObject(ctx);
190
+ JS_SetPropertyStr(ctx, obj, "entries", JS_NewInt32(ctx, static_cast<int32_t>(used_count)));
191
+ JS_SetPropertyStr(ctx, obj, "used", JS_NewInt32(ctx, static_cast<int32_t>(stats.used_entries)));
192
+ JS_SetPropertyStr(ctx, obj, "total", JS_NewInt32(ctx, static_cast<int32_t>(stats.total_entries)));
193
+ JS_SetPropertyStr(ctx, obj, "free", JS_NewInt32(ctx, static_cast<int32_t>(stats.free_entries)));
194
+ return obj;
195
+ }
196
+
197
+ /* ── Module init ─────────────────────────────────────────────────── */
198
+
199
+ static int mik__nvs_kv_module_init(JSContext* ctx, JSModuleDef* m) {
200
+ JS_SetModuleExport(ctx, m, "set", JS_NewCFunction(ctx, mik__nvs_kv_set, "set", 2));
201
+ JS_SetModuleExport(ctx, m, "get", JS_NewCFunction(ctx, mik__nvs_kv_get, "get", 1));
202
+ JS_SetModuleExport(ctx, m, "remove", JS_NewCFunction(ctx, mik__nvs_kv_remove, "remove", 1));
203
+ JS_SetModuleExport(ctx, m, "clear", JS_NewCFunction(ctx, mik__nvs_kv_clear, "clear", 0));
204
+ JS_SetModuleExport(ctx, m, "info", JS_NewCFunction(ctx, mik__nvs_kv_info, "info", 0));
205
+ return 0;
206
+ }
207
+
208
+ static JSModuleDef* mik__nvs_kv_init(JSContext* ctx) {
209
+ JSModuleDef* m = JS_NewCModule(ctx, "native:nvs_kv", mik__nvs_kv_module_init);
210
+ if (!m) return nullptr;
211
+ JS_AddModuleExport(ctx, m, "set");
212
+ JS_AddModuleExport(ctx, m, "get");
213
+ JS_AddModuleExport(ctx, m, "remove");
214
+ JS_AddModuleExport(ctx, m, "clear");
215
+ JS_AddModuleExport(ctx, m, "info");
216
+ return m;
217
+ }
218
+
219
+ MIK_REGISTER_MODULE(nvs_kv, "native:nvs_kv", mik__nvs_kv_init, nullptr, nullptr)