@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,226 @@
1
+ #include "driver/gpio.h"
2
+ #include "esp_sleep.h"
3
+ #include "soc/soc_caps.h"
4
+
5
+ #include "mikrojs/private.h"
6
+ #include "mikrojs/utils.h"
7
+
8
+ static const char* mik__wakeup_cause_str(uint32_t causes) {
9
+ if (causes & BIT(ESP_SLEEP_WAKEUP_TIMER)) return "timer";
10
+ if (causes & BIT(ESP_SLEEP_WAKEUP_EXT0)) return "ext0";
11
+ if (causes & BIT(ESP_SLEEP_WAKEUP_EXT1)) return "ext1";
12
+ if (causes & BIT(ESP_SLEEP_WAKEUP_GPIO)) return "gpio";
13
+ if (causes & BIT(ESP_SLEEP_WAKEUP_TOUCHPAD)) return "touchpad";
14
+ if (causes & BIT(ESP_SLEEP_WAKEUP_ULP)) return "ulp";
15
+ return "undefined";
16
+ }
17
+
18
+ /* ── native:sleep JS module ─────────────────────────────────────────── */
19
+
20
+ static JSValue mik__sleep_deep(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
21
+ int64_t ms;
22
+ if (JS_ToInt64(ctx, &ms, argv[0])) return JS_EXCEPTION;
23
+
24
+ if (ms > 0) {
25
+ esp_err_t err = esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(ms) * 1000);
26
+ if (err != ESP_OK)
27
+ return JS_ThrowInternalError(ctx, "timer wakeup failed: %s", esp_err_to_name(err));
28
+ }
29
+
30
+ /* Note: we intentionally do NOT call MIK_FreeRuntime() here. We are inside
31
+ * a JS function call, so the runtime still has live GC objects on the call
32
+ * stack — freeing it would trigger a QuickJS assert. Since deep sleep
33
+ * reboots the chip, all memory is reclaimed anyway. */
34
+ esp_deep_sleep_start();
35
+ /* Never reached */
36
+ __builtin_unreachable();
37
+ }
38
+
39
+ static JSValue mik__sleep_light(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
40
+ int64_t ms;
41
+ if (JS_ToInt64(ctx, &ms, argv[0])) return JS_EXCEPTION;
42
+
43
+ if (ms > 0) {
44
+ esp_err_t err = esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(ms) * 1000);
45
+ if (err != ESP_OK)
46
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
47
+ "failed to enable timer wakeup: %s",
48
+ esp_err_to_name(err));
49
+ }
50
+
51
+ esp_err_t err = esp_light_sleep_start();
52
+ if (err != ESP_OK)
53
+ return mik__result_err_named(ctx, "LightSleepFailed",
54
+ "light sleep failed: %s", esp_err_to_name(err));
55
+
56
+ return mik__result_ok_void(ctx);
57
+ }
58
+
59
+ static JSValue mik__sleep_get_wakeup_cause(JSContext* ctx, JSValue this_val, int argc,
60
+ JSValue* argv) {
61
+ uint32_t causes = esp_sleep_get_wakeup_causes();
62
+ return JS_NewString(ctx, mik__wakeup_cause_str(causes));
63
+ }
64
+
65
+ static JSValue mik__sleep_enable_timer_wakeup(JSContext* ctx, JSValue this_val, int argc,
66
+ JSValue* argv) {
67
+ int64_t us;
68
+ if (JS_ToInt64(ctx, &us, argv[0])) return JS_EXCEPTION;
69
+
70
+ esp_err_t err = esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(us));
71
+ if (err != ESP_OK)
72
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
73
+ "failed to enable timer wakeup: %s", esp_err_to_name(err));
74
+
75
+ return mik__result_ok_void(ctx);
76
+ }
77
+
78
+ static JSValue mik__sleep_enable_gpio_wakeup(JSContext* ctx, JSValue this_val, int argc,
79
+ JSValue* argv) {
80
+ int32_t pin, level;
81
+ if (JS_ToInt32(ctx, &pin, argv[0])) return JS_EXCEPTION;
82
+ if (JS_ToInt32(ctx, &level, argv[1])) return JS_EXCEPTION;
83
+
84
+ esp_err_t err =
85
+ gpio_wakeup_enable(static_cast<gpio_num_t>(pin), static_cast<gpio_int_type_t>(level));
86
+ if (err != ESP_OK)
87
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
88
+ "failed to enable GPIO wakeup on pin %d: %s", pin,
89
+ esp_err_to_name(err));
90
+
91
+ err = esp_sleep_enable_gpio_wakeup();
92
+ if (err != ESP_OK)
93
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
94
+ "failed to enable GPIO wakeup source: %s",
95
+ esp_err_to_name(err));
96
+
97
+ return mik__result_ok_void(ctx);
98
+ }
99
+
100
+ /* EXT0 / EXT1 wakeup is chip-specific (not on ESP32-C3, C6, H2). We still
101
+ * register the JS exports unconditionally so `mikrojs/sleep`'s static
102
+ * re-exports resolve on every target; the function bodies return a
103
+ * `WakeupConfigFailed` error on chips where the capability is missing. */
104
+ static JSValue mik__sleep_enable_ext0_wakeup(JSContext* ctx, JSValue this_val, int argc,
105
+ JSValue* argv) {
106
+ #if SOC_PM_SUPPORT_EXT0_WAKEUP
107
+ int32_t pin, level;
108
+ if (JS_ToInt32(ctx, &pin, argv[0])) return JS_EXCEPTION;
109
+ if (JS_ToInt32(ctx, &level, argv[1])) return JS_EXCEPTION;
110
+
111
+ esp_err_t err = esp_sleep_enable_ext0_wakeup(static_cast<gpio_num_t>(pin), level);
112
+ if (err != ESP_OK)
113
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
114
+ "failed to enable EXT0 wakeup on pin %d: %s", pin,
115
+ esp_err_to_name(err));
116
+
117
+ return mik__result_ok_void(ctx);
118
+ #else
119
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
120
+ "EXT0 wakeup is not supported on this chip");
121
+ #endif
122
+ }
123
+
124
+ static JSValue mik__sleep_enable_ext1_wakeup(JSContext* ctx, JSValue this_val, int argc,
125
+ JSValue* argv) {
126
+ #if SOC_PM_SUPPORT_EXT1_WAKEUP
127
+ int64_t pin_mask;
128
+ int32_t mode;
129
+ if (JS_ToInt64(ctx, &pin_mask, argv[0])) return JS_EXCEPTION;
130
+ if (JS_ToInt32(ctx, &mode, argv[1])) return JS_EXCEPTION;
131
+
132
+ esp_err_t err = esp_sleep_enable_ext1_wakeup(static_cast<uint64_t>(pin_mask),
133
+ static_cast<esp_sleep_ext1_wakeup_mode_t>(mode));
134
+ if (err != ESP_OK)
135
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
136
+ "failed to enable EXT1 wakeup: %s", esp_err_to_name(err));
137
+
138
+ return mik__result_ok_void(ctx);
139
+ #else
140
+ return mik__result_err_named(ctx, "WakeupConfigFailed",
141
+ "EXT1 wakeup is not supported on this chip");
142
+ #endif
143
+ }
144
+
145
+ static JSValue mik__sleep_disable_wakeup_source(JSContext* ctx, JSValue this_val, int argc,
146
+ JSValue* argv) {
147
+ esp_sleep_source_t source = ESP_SLEEP_WAKEUP_ALL;
148
+
149
+ if (argc > 0 && !JS_IsUndefined(argv[0])) {
150
+ const char* str = JS_ToCString(ctx, argv[0]);
151
+ if (!str) return JS_EXCEPTION;
152
+
153
+ bool valid = true;
154
+ if (strcmp(str, "timer") == 0)
155
+ source = ESP_SLEEP_WAKEUP_TIMER;
156
+ else if (strcmp(str, "ext0") == 0)
157
+ source = ESP_SLEEP_WAKEUP_EXT0;
158
+ else if (strcmp(str, "ext1") == 0)
159
+ source = ESP_SLEEP_WAKEUP_EXT1;
160
+ else if (strcmp(str, "gpio") == 0)
161
+ source = ESP_SLEEP_WAKEUP_GPIO;
162
+ else if (strcmp(str, "touchpad") == 0)
163
+ source = ESP_SLEEP_WAKEUP_TOUCHPAD;
164
+ else if (strcmp(str, "ulp") == 0)
165
+ source = ESP_SLEEP_WAKEUP_ULP;
166
+ else
167
+ valid = false;
168
+
169
+ if (!valid) {
170
+ JSValue err = JS_ThrowRangeError(ctx, "unknown wakeup source: %s", str);
171
+ JS_FreeCString(ctx, str);
172
+ return err;
173
+ }
174
+ JS_FreeCString(ctx, str);
175
+ }
176
+
177
+ esp_err_t err = esp_sleep_disable_wakeup_source(source);
178
+ if (err != ESP_OK)
179
+ return mik__result_err_named(ctx, "DisableWakeupFailed",
180
+ "failed to disable wakeup source: %s", esp_err_to_name(err));
181
+
182
+ return mik__result_ok_void(ctx);
183
+ }
184
+
185
+ /* ── Module init ─────────────────────────────────────────────────── */
186
+
187
+ static int mik__sleep_module_init(JSContext* ctx, JSModuleDef* m) {
188
+ JS_SetModuleExport(ctx, m, "deepSleep",
189
+ JS_NewCFunction(ctx, mik__sleep_deep, "deepSleep", 1));
190
+ JS_SetModuleExport(ctx, m, "lightSleep",
191
+ JS_NewCFunction(ctx, mik__sleep_light, "lightSleep", 1));
192
+ JS_SetModuleExport(ctx, m, "getWakeupCause",
193
+ JS_NewCFunction(ctx, mik__sleep_get_wakeup_cause, "getWakeupCause", 0));
194
+ JS_SetModuleExport(
195
+ ctx, m, "enableTimerWakeup",
196
+ JS_NewCFunction(ctx, mik__sleep_enable_timer_wakeup, "enableTimerWakeup", 1));
197
+ JS_SetModuleExport(
198
+ ctx, m, "enableGpioWakeup",
199
+ JS_NewCFunction(ctx, mik__sleep_enable_gpio_wakeup, "enableGpioWakeup", 2));
200
+ JS_SetModuleExport(
201
+ ctx, m, "enableExt0Wakeup",
202
+ JS_NewCFunction(ctx, mik__sleep_enable_ext0_wakeup, "enableExt0Wakeup", 2));
203
+ JS_SetModuleExport(
204
+ ctx, m, "enableExt1Wakeup",
205
+ JS_NewCFunction(ctx, mik__sleep_enable_ext1_wakeup, "enableExt1Wakeup", 2));
206
+ JS_SetModuleExport(
207
+ ctx, m, "disableWakeupSource",
208
+ JS_NewCFunction(ctx, mik__sleep_disable_wakeup_source, "disableWakeupSource", 1));
209
+ return 0;
210
+ }
211
+
212
+ static JSModuleDef* mik__sleep_init(JSContext* ctx) {
213
+ JSModuleDef* m = JS_NewCModule(ctx, "native:sleep", mik__sleep_module_init);
214
+ if (!m) return nullptr;
215
+ JS_AddModuleExport(ctx, m, "deepSleep");
216
+ JS_AddModuleExport(ctx, m, "lightSleep");
217
+ JS_AddModuleExport(ctx, m, "getWakeupCause");
218
+ JS_AddModuleExport(ctx, m, "enableTimerWakeup");
219
+ JS_AddModuleExport(ctx, m, "enableGpioWakeup");
220
+ JS_AddModuleExport(ctx, m, "enableExt0Wakeup");
221
+ JS_AddModuleExport(ctx, m, "enableExt1Wakeup");
222
+ JS_AddModuleExport(ctx, m, "disableWakeupSource");
223
+ return m;
224
+ }
225
+
226
+ MIK_REGISTER_MODULE(sleep, "native:sleep", mik__sleep_init, nullptr, nullptr)
@@ -0,0 +1,275 @@
1
+ #include <cstring>
2
+ #include <ctime>
3
+ #include <sys/time.h>
4
+
5
+ #include "esp_log.h"
6
+ #include "esp_netif_sntp.h"
7
+ #include "esp_sntp.h"
8
+ #include "freertos/FreeRTOS.h"
9
+ #include "freertos/queue.h"
10
+ #include "mikrojs/private.h"
11
+ #include "mikrojs/utils.h"
12
+
13
+ #define MIK_SNTP_TAG "native:sntp"
14
+ #define MIK_SNTP_MAX_SERVERS 3
15
+
16
+ /* ── SNTP state (attached to MIKRuntime) ───────────────────────────── */
17
+
18
+ struct MIKSntpState {
19
+ QueueHandle_t event_queue;
20
+ MIKPromise sync_promise;
21
+ bool sync_pending;
22
+ bool running;
23
+ bool background;
24
+ };
25
+
26
+ /* Dynamic module data slot, allocated on first import */
27
+ static int mik__sntp_slot = -1;
28
+
29
+ static inline MIKSntpState*& mik__sntp_st(MIKRuntime* rt) {
30
+ return reinterpret_cast<MIKSntpState*&>(rt->module_data[mik__sntp_slot]);
31
+ }
32
+
33
+ /* Global queue pointer for the sync callback (runs in lwIP timer task) */
34
+ static QueueHandle_t s_event_queue = nullptr;
35
+
36
+ /* ── SNTP sync callback ────────────────────────────────────────────── */
37
+
38
+ static void mik__sntp_sync_cb(struct timeval* tv) {
39
+ if (!s_event_queue) return;
40
+ struct timeval evt = *tv;
41
+ xQueueSend(s_event_queue, &evt, 0);
42
+ }
43
+
44
+ /* ── Test helper: inject a fake sync event ─────────────────────────── */
45
+
46
+ void mik__sntp_inject_sync(int64_t epoch_sec) {
47
+ if (!s_event_queue) return;
48
+ struct timeval tv = {.tv_sec = static_cast<time_t>(epoch_sec), .tv_usec = 0};
49
+ xQueueSend(s_event_queue, &tv, 0);
50
+ }
51
+
52
+ /* ── JS functions ──────────────────────────────────────────────────── */
53
+
54
+ static JSValue mik__sntp_sync(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
55
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
56
+ CHECK_NOT_NULL(mik_rt);
57
+ MIKSntpState* state = mik__sntp_st(mik_rt);
58
+ CHECK_NOT_NULL(state);
59
+
60
+ /* arg 0: servers (array of strings) */
61
+ /* arg 1: timezone (string) */
62
+ /* arg 2: background (bool) */
63
+
64
+ if (!JS_IsArray(argv[0])) {
65
+ return JS_ThrowTypeError(ctx, "expected servers to be an array");
66
+ }
67
+
68
+ /* Extract server strings */
69
+ JSValue len_val = JS_GetPropertyStr(ctx, argv[0], "length");
70
+ int32_t server_count = 0;
71
+ JS_ToInt32(ctx, &server_count, len_val);
72
+ JS_FreeValue(ctx, len_val);
73
+
74
+ if (server_count <= 0 || server_count > MIK_SNTP_MAX_SERVERS) {
75
+ return JS_ThrowRangeError(ctx, "expected 1-%d servers, got %d", MIK_SNTP_MAX_SERVERS,
76
+ server_count);
77
+ }
78
+
79
+ const char* servers[MIK_SNTP_MAX_SERVERS] = {};
80
+ for (int32_t i = 0; i < server_count; i++) {
81
+ JSValue elem = JS_GetPropertyUint32(ctx, argv[0], i);
82
+ servers[i] = JS_ToCString(ctx, elem);
83
+ JS_FreeValue(ctx, elem);
84
+ if (!servers[i]) {
85
+ for (int32_t j = 0; j < i; j++) JS_FreeCString(ctx, servers[j]);
86
+ return JS_EXCEPTION;
87
+ }
88
+ }
89
+
90
+ /* Set timezone */
91
+ const char* tz = JS_ToCString(ctx, argv[1]);
92
+ if (!tz) {
93
+ for (int32_t i = 0; i < server_count; i++) JS_FreeCString(ctx, servers[i]);
94
+ return JS_EXCEPTION;
95
+ }
96
+ setenv("TZ", tz, 1);
97
+ tzset();
98
+ JS_FreeCString(ctx, tz);
99
+
100
+ bool background = JS_ToBool(ctx, argv[2]);
101
+
102
+ /* If already running, tear down first */
103
+ if (state->running) {
104
+ if (state->sync_pending) {
105
+ JSValue cancel_err = mik__result_err_tag(ctx, "Cancelled");
106
+ MIK_ResolvePromise(ctx, &state->sync_promise, 1, &cancel_err);
107
+ state->sync_pending = false;
108
+ }
109
+ esp_netif_sntp_deinit();
110
+ state->running = false;
111
+
112
+ /* Drain queue */
113
+ struct timeval discard;
114
+ while (xQueueReceive(state->event_queue, &discard, 0) == pdTRUE) {
115
+ }
116
+ }
117
+
118
+ /* Configure SNTP */
119
+ esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(servers[0]);
120
+ config.sync_cb = mik__sntp_sync_cb;
121
+
122
+ /* Add additional servers if provided */
123
+ if (server_count > 1) {
124
+ config.num_of_servers = server_count;
125
+ /* The first server is already set by the macro. Set additional ones after init. */
126
+ }
127
+
128
+ esp_err_t err = esp_netif_sntp_init(&config);
129
+ if (err != ESP_OK) {
130
+ for (int32_t i = 0; i < server_count; i++) JS_FreeCString(ctx, servers[i]);
131
+ return mik__result_err_named(ctx, "InitFailed",
132
+ "SNTP init failed: %s", esp_err_to_name(err));
133
+ }
134
+
135
+ /* Set additional servers (index 1+) */
136
+ for (int32_t i = 1; i < server_count; i++) {
137
+ esp_sntp_setservername(i, servers[i]);
138
+ }
139
+
140
+ for (int32_t i = 0; i < server_count; i++) JS_FreeCString(ctx, servers[i]);
141
+
142
+ state->running = true;
143
+ state->background = background;
144
+
145
+ /* Create and return promise wrapped in result */
146
+ JSValue promise = MIK_InitPromise(ctx, &state->sync_promise);
147
+ state->sync_pending = true;
148
+
149
+ return mik__result_ok(ctx, promise);
150
+ }
151
+
152
+ static JSValue mik__sntp_stop(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
153
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
154
+ CHECK_NOT_NULL(mik_rt);
155
+ MIKSntpState* state = mik__sntp_st(mik_rt);
156
+ CHECK_NOT_NULL(state);
157
+
158
+ if (state->running) {
159
+ esp_netif_sntp_deinit();
160
+ state->running = false;
161
+ }
162
+
163
+ return JS_UNDEFINED;
164
+ }
165
+
166
+ static JSValue mik__sntp_set_timezone(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
167
+ const char* tz = JS_ToCString(ctx, argv[0]);
168
+ if (!tz) return JS_EXCEPTION;
169
+ setenv("TZ", tz, 1);
170
+ tzset();
171
+ JS_FreeCString(ctx, tz);
172
+ return JS_UNDEFINED;
173
+ }
174
+
175
+ /* ── Module init ───────────────────────────────────────────────────── */
176
+
177
+ static int mik__sntp_module_init(JSContext* ctx, JSModuleDef* m) {
178
+ JS_SetModuleExport(ctx, m, "sync", JS_NewCFunction(ctx, mik__sntp_sync, "sync", 3));
179
+ JS_SetModuleExport(ctx, m, "stop", JS_NewCFunction(ctx, mik__sntp_stop, "stop", 0));
180
+ JS_SetModuleExport(ctx, m, "setTimezone",
181
+ JS_NewCFunction(ctx, mik__sntp_set_timezone, "setTimezone", 1));
182
+ return 0;
183
+ }
184
+
185
+ /* ── Public API ────────────────────────────────────────────────────── */
186
+
187
+ static JSModuleDef* mik__sntp_init(JSContext* ctx) {
188
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
189
+ CHECK_NOT_NULL(mik_rt);
190
+ mik__sntp_slot = MIK_AllocModuleSlot(mik_rt);
191
+
192
+ auto* state = new MIKSntpState();
193
+ state->event_queue = xQueueCreate(4, sizeof(struct timeval));
194
+ CHECK_NOT_NULL(state->event_queue);
195
+ state->sync_pending = false;
196
+ state->running = false;
197
+ state->background = false;
198
+
199
+ s_event_queue = state->event_queue;
200
+ mik__sntp_st(mik_rt) = state;
201
+
202
+ JSModuleDef* m = JS_NewCModule(ctx, "native:sntp", mik__sntp_module_init);
203
+ if (!m) {
204
+ /* The loop consumer (mik__sntp_destroy) is only registered when init
205
+ * returns a non-null module (see modules.cpp). Clean up manually. */
206
+ s_event_queue = nullptr;
207
+ vQueueDelete(state->event_queue);
208
+ delete state;
209
+ mik__sntp_st(mik_rt) = nullptr;
210
+ return nullptr;
211
+ }
212
+ JS_AddModuleExport(ctx, m, "sync");
213
+ JS_AddModuleExport(ctx, m, "stop");
214
+ JS_AddModuleExport(ctx, m, "setTimezone");
215
+ return m;
216
+ }
217
+
218
+ void mik__sntp_consume(JSContext* ctx) {
219
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
220
+ CHECK_NOT_NULL(mik_rt);
221
+ if (!mik__sntp_st(mik_rt)) return;
222
+
223
+ MIKSntpState* state = mik__sntp_st(mik_rt);
224
+ struct timeval tv;
225
+
226
+ while (xQueueReceive(state->event_queue, &tv, 0) == pdTRUE) {
227
+ if (state->sync_pending) {
228
+ /* Build result object: { time: <milliseconds since epoch> } */
229
+ double time_ms =
230
+ static_cast<double>(tv.tv_sec) * 1000.0 + static_cast<double>(tv.tv_usec) / 1000.0;
231
+ JSValue time_obj = JS_NewObject(ctx);
232
+ JS_SetPropertyStr(ctx, time_obj, "time", JS_NewFloat64(ctx, time_ms));
233
+ JSValue result = mik__result_ok(ctx, time_obj);
234
+
235
+ MIK_ResolvePromise(ctx, &state->sync_promise, 1, &result);
236
+ state->sync_pending = false;
237
+
238
+ /* If not background mode, stop SNTP after first sync */
239
+ if (!state->background) {
240
+ esp_netif_sntp_deinit();
241
+ state->running = false;
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ void mik__sntp_destroy(JSContext* ctx) {
248
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
249
+ CHECK_NOT_NULL(mik_rt);
250
+ if (!mik__sntp_st(mik_rt)) return;
251
+
252
+ MIKSntpState* state = mik__sntp_st(mik_rt);
253
+
254
+ if (state->running) {
255
+ esp_netif_sntp_deinit();
256
+ state->running = false;
257
+ }
258
+
259
+ if (state->sync_pending) {
260
+ MIK_FreePromise(ctx, &state->sync_promise);
261
+ state->sync_pending = false;
262
+ }
263
+
264
+ /* Drain queue */
265
+ struct timeval discard;
266
+ while (xQueueReceive(state->event_queue, &discard, 0) == pdTRUE) {
267
+ }
268
+
269
+ s_event_queue = nullptr;
270
+ vQueueDelete(state->event_queue);
271
+ delete state;
272
+ mik__sntp_st(mik_rt) = nullptr;
273
+ }
274
+
275
+ MIK_REGISTER_MODULE(sntp, "native:sntp", mik__sntp_init, mik__sntp_consume, mik__sntp_destroy)