@mikrojs/firmware 0.8.0-pr-115.g87a99f9 → 0.8.0

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.
@@ -64,6 +64,16 @@ void mik_logfile_resume(void);
64
64
  /* Serial binary I/O (mik_serial_io.cpp) */
65
65
  void mik__serial_binary_begin_no_echo(void);
66
66
 
67
+ /* Detach the USB Serial/JTAG peripheral from the host (uninstalls the
68
+ * driver and disables the PHY pad) so the CLI sees a clean disconnect
69
+ * before light sleep. No-op when the active console isn't USJ. */
70
+ void mik__serial_io_detach_usb(void);
71
+
72
+ /* Re-attach the USB Serial/JTAG peripheral after a prior detach. The
73
+ * host re-enumerates the device. No-op when the active console isn't
74
+ * USJ. */
75
+ void mik__serial_io_attach_usb(void);
76
+
67
77
  /* Shared NVS helpers (mik_serial_io.cpp) */
68
78
  extern const char* MIK__NVS_NS_ENV; /* env var values */
69
79
  extern const char* MIK__NVS_NS_SEC; /* secret-flag markers (u8) */
@@ -17,6 +17,8 @@
17
17
 
18
18
  #if SOC_USB_SERIAL_JTAG_SUPPORTED
19
19
  #include "driver/usb_serial_jtag.h"
20
+ #include "esp_rom_sys.h"
21
+ #include "hal/usb_serial_jtag_ll.h"
20
22
  /* mikrojs owns the USB-Serial/JTAG peripheral directly. Leaving
21
23
  * CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y would have ESP-IDF register a
22
24
  * VFS and write to the TX FIFO from stdio, racing our driver ISR. */
@@ -74,35 +76,41 @@ static enum { CONSOLE_UART, CONSOLE_USB_SERIAL_JTAG } s_console =
74
76
  static bool s_usj_installed = false;
75
77
  #endif
76
78
 
77
- void mik__console_init(void) {
78
79
  #if SOC_USB_SERIAL_JTAG_SUPPORTED
79
- /* Install USB-Serial/JTAG via the public driver API only. No VFS
80
- * registration, no `fileno(stdin)`, no newlib integration — that's
81
- * the whole point of this path.
82
- *
83
- * Both rings are sized to fit the largest single TLV frame plus a
84
- * little headroom for the next frame's header to land alongside it.
85
- *
86
- * RX (host → device): the largest single command frame is a
87
- * CMD_DEPLOY_PUT_CHUNK (≤ 2 KB body + 5 B header). Deploy PUTs no
88
- * longer carry the file body in one frame — they're streamed as
89
- * begin + N chunks with per-chunk MSG_OK pacing — so this size is
90
- * a function of the chunk size, not of the largest deployable file.
91
- * 4 KB gives ~2× headroom over a single chunk so the next command
92
- * can be queued behind it.
93
- *
94
- * TX (device → host): bumped from the IDF default (256 B) to 2 KB
95
- * so MSG_READY resends during the boot-handshake window don't fill
96
- * the ring before the CLI's TTY starts draining. At 256 B the ring
97
- * fills after ~3 MSG_READY sends, then subsequent writes (including
98
- * the MSG_OK response to the first CMD_RUNTIME_PAUSE) stall long
99
- * enough to blow past the CLI's 10 s pause timeout and force a
100
- * restart-and-retry on every `mikro dev`. 2 KB is still small
101
- * enough not to meaningfully dent RAM for display-heavy apps. */
80
+ /* Install USB-Serial/JTAG via the public driver API only. No VFS
81
+ * registration, no `fileno(stdin)`, no newlib integration — that's
82
+ * the whole point of this path.
83
+ *
84
+ * Both rings are sized to fit the largest single TLV frame plus a
85
+ * little headroom for the next frame's header to land alongside it.
86
+ *
87
+ * RX (host → device): the largest single command frame is a
88
+ * CMD_DEPLOY_PUT_CHUNK (≤ 2 KB body + 5 B header). Deploy PUTs no
89
+ * longer carry the file body in one frame — they're streamed as
90
+ * begin + N chunks with per-chunk MSG_OK pacing — so this size is
91
+ * a function of the chunk size, not of the largest deployable file.
92
+ * 4 KB gives ~2× headroom over a single chunk so the next command
93
+ * can be queued behind it.
94
+ *
95
+ * TX (device → host): bumped from the IDF default (256 B) to 2 KB
96
+ * so MSG_READY resends during the boot-handshake window don't fill
97
+ * the ring before the CLI's TTY starts draining. At 256 B the ring
98
+ * fills after ~3 MSG_READY sends, then subsequent writes (including
99
+ * the MSG_OK response to the first CMD_RUNTIME_PAUSE) stall long
100
+ * enough to blow past the CLI's 10 s pause timeout and force a
101
+ * restart-and-retry on every `mikro dev`. 2 KB is still small
102
+ * enough not to meaningfully dent RAM for display-heavy apps. */
103
+ static bool mik__usj_install_with_default_config(void) {
102
104
  usb_serial_jtag_driver_config_t usj_cfg = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
103
105
  usj_cfg.rx_buffer_size = 4096;
104
106
  usj_cfg.tx_buffer_size = 2048;
105
- s_usj_installed = (usb_serial_jtag_driver_install(&usj_cfg) == ESP_OK);
107
+ return usb_serial_jtag_driver_install(&usj_cfg) == ESP_OK;
108
+ }
109
+ #endif
110
+
111
+ void mik__console_init(void) {
112
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
113
+ s_usj_installed = mik__usj_install_with_default_config();
106
114
  #endif
107
115
 
108
116
  #if MIK_CONSOLE_HAS_UART
@@ -228,6 +236,84 @@ void mik__serial_binary_begin_no_echo(void) {
228
236
  * so `\n` passes through unchanged on both USJ and UART paths. */
229
237
  }
230
238
 
239
+ /* ── USB detach/attach for light sleep ─────────────────────────────
240
+ *
241
+ * Light sleep on USJ-console chips (C3, C6, S3, …) powers down the
242
+ * digital peripheral domain but leaves the USB device enumerated on
243
+ * the host. node-serialport doesn't see a close, so the CLI can't
244
+ * tell the difference between "device is busy" and "device is asleep".
245
+ *
246
+ * To make the disconnect visible we uninstall the driver and pull
247
+ * down the D+ pad before sleeping. On wake we restore the pad and
248
+ * re-install the driver. The host re-enumerates, the CLI's existing
249
+ * disconnect → reconnect path handles the rest. */
250
+
251
+ void mik__serial_io_detach_usb(void) {
252
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
253
+ if (s_console != CONSOLE_USB_SERIAL_JTAG) return;
254
+ if (!s_usj_installed) return;
255
+
256
+ /* USJ has no public flush API. A short delay covers TX-ring drain
257
+ * for the typical log-line-sized writes we send before sleeping. */
258
+ vTaskDelay(pdMS_TO_TICKS(20));
259
+
260
+ usb_serial_jtag_driver_uninstall();
261
+ s_usj_installed = false;
262
+
263
+ /* Force a host-visible disconnect by overriding D+/D- pulls to
264
+ * present SE0 on the bus. `usb_serial_jtag_ll_phy_enable_pad(false)`
265
+ * (which ESP-IDF's own sleep prep uses) only gates the internal pad
266
+ * routing for leakage — it does NOT release the host-visible 1.5 k
267
+ * D+ pullup, so the host still sees the device enumerated. The
268
+ * pull-override is the only mechanism in ESP-IDF v6.0.1 that
269
+ * actually triggers a re-enumerate. C6 has no `_SUPPORT_LIGHT_SLEEP`
270
+ * cap for USJ (IDF-6395 in soc_caps.h), so this is the supported
271
+ * path.
272
+ * Refs:
273
+ * components/hal/include/hal/usb_serial_jtag_ll.h
274
+ * docs.espressif.com/projects/esp-iot-solution/.../usb_serial_jtag.html
275
+ */
276
+ usb_serial_jtag_pull_override_vals_t off = {
277
+ .dp_pu = 0,
278
+ .dm_pu = 0,
279
+ .dp_pd = 1,
280
+ .dm_pd = 1,
281
+ };
282
+ usb_serial_jtag_ll_phy_enable_pull_override(&off);
283
+ /* >2.5 ms of SE0 so the host registers a disconnect rather than a
284
+ * USB suspend. 5 ms gives plenty of margin without meaningfully
285
+ * delaying sleep entry. */
286
+ esp_rom_delay_us(5000);
287
+ #endif
288
+ }
289
+
290
+ void mik__serial_io_attach_usb(void) {
291
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
292
+ if (s_console != CONSOLE_USB_SERIAL_JTAG) return;
293
+ if (s_usj_installed) return;
294
+
295
+ /* `disable_pull_override` only flips `pad_pull_override` back to 0;
296
+ * the `dp_pullup`/`dp_pulldown` register fields still hold the
297
+ * disconnect values from detach (D+ pullup off, D+ pulldown on),
298
+ * so the host sees no connect signal until the peripheral autopilots
299
+ * — which only happens reliably once the driver is active. Drive
300
+ * the override to an explicit "connect" state (D+ pullup on,
301
+ * everything else off) first so the host sees J-state immediately,
302
+ * install the driver while the override holds it, then release. */
303
+ usb_serial_jtag_pull_override_vals_t connect = {
304
+ .dp_pu = 1,
305
+ .dm_pu = 0,
306
+ .dp_pd = 0,
307
+ .dm_pd = 0,
308
+ };
309
+ usb_serial_jtag_ll_phy_enable_pull_override(&connect);
310
+
311
+ s_usj_installed = mik__usj_install_with_default_config();
312
+
313
+ usb_serial_jtag_ll_phy_disable_pull_override();
314
+ #endif
315
+ }
316
+
231
317
  /* ── NVS helpers ─────────────────────────────────────────────────── */
232
318
 
233
319
  const char* MIK__NVS_NS_ENV = "mik.env";
@@ -1,9 +1,33 @@
1
1
  #include "driver/gpio.h"
2
+ #include "driver/rtc_io.h"
2
3
  #include "esp_sleep.h"
3
4
  #include "soc/soc_caps.h"
4
5
 
5
6
  #include "mikrojs/private.h"
6
7
  #include "mikrojs/utils.h"
8
+ #include "mikrojs_esp32.h"
9
+
10
+ /* For deep-sleep wakeup, the chip's digital pad config (set by `pinMode`)
11
+ * doesn't persist — only the RTC-IO pad does. Without an explicit RTC
12
+ * pull, EXT0/EXT1 pins float and trigger spurious wakes. Configure the
13
+ * RTC pull to the opposite of the wake direction so the pin idles in
14
+ * the non-trigger state and a button press flips it. */
15
+ static void mik__rtc_pull_for_wake(int pin, bool wake_on_high) {
16
+ #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
17
+ if (!rtc_gpio_is_valid_gpio(static_cast<gpio_num_t>(pin))) return;
18
+ gpio_num_t g = static_cast<gpio_num_t>(pin);
19
+ rtc_gpio_pullup_dis(g);
20
+ rtc_gpio_pulldown_dis(g);
21
+ if (wake_on_high) {
22
+ rtc_gpio_pulldown_en(g);
23
+ } else {
24
+ rtc_gpio_pullup_en(g);
25
+ }
26
+ #else
27
+ (void)pin;
28
+ (void)wake_on_high;
29
+ #endif
30
+ }
7
31
 
8
32
  static const char* mik__wakeup_cause_str(uint32_t causes) {
9
33
  if (causes & BIT(ESP_SLEEP_WAKEUP_TIMER)) return "timer";
@@ -15,17 +39,221 @@ static const char* mik__wakeup_cause_str(uint32_t causes) {
15
39
  return "undefined";
16
40
  }
17
41
 
42
+ /* ── Wakeup source configuration ───────────────────────────────────── */
43
+
44
+ static bool mik__configure_timer(JSContext* ctx, JSValue sources) {
45
+ JSValue v = JS_GetPropertyStr(ctx, sources, "timer");
46
+ bool ok = true;
47
+ if (!JS_IsUndefined(v)) {
48
+ double ms;
49
+ if (JS_ToFloat64(ctx, &ms, v)) {
50
+ ok = false;
51
+ } else if (ms < 0) {
52
+ JS_ThrowRangeError(ctx, "timer must be >= 0");
53
+ ok = false;
54
+ } else {
55
+ uint64_t us = static_cast<uint64_t>(ms * 1000.0);
56
+ esp_err_t err = esp_sleep_enable_timer_wakeup(us);
57
+ if (err != ESP_OK) {
58
+ JS_ThrowInternalError(ctx, "timer wakeup failed: %s", esp_err_to_name(err));
59
+ ok = false;
60
+ }
61
+ }
62
+ }
63
+ JS_FreeValue(ctx, v);
64
+ return ok;
65
+ }
66
+
67
+ static bool mik__read_level(JSContext* ctx, JSValue obj, const char* key, int* out_level) {
68
+ JSValue v = JS_GetPropertyStr(ctx, obj, key);
69
+ const char* s = JS_ToCString(ctx, v);
70
+ JS_FreeValue(ctx, v);
71
+ if (!s) return false;
72
+ bool ok = true;
73
+ if (strcmp(s, "high") == 0)
74
+ *out_level = 1;
75
+ else if (strcmp(s, "low") == 0)
76
+ *out_level = 0;
77
+ else {
78
+ JS_ThrowRangeError(ctx, "%s must be 'high' or 'low'", key);
79
+ ok = false;
80
+ }
81
+ JS_FreeCString(ctx, s);
82
+ return ok;
83
+ }
84
+
85
+ static bool mik__configure_gpio(JSContext* ctx, JSValue sources) {
86
+ JSValue gpio = JS_GetPropertyStr(ctx, sources, "gpio");
87
+ if (JS_IsUndefined(gpio)) {
88
+ JS_FreeValue(ctx, gpio);
89
+ return true;
90
+ }
91
+
92
+ JSValue pin_val = JS_GetPropertyStr(ctx, gpio, "pin");
93
+ int32_t pin;
94
+ bool ok = !JS_ToInt32(ctx, &pin, pin_val);
95
+ JS_FreeValue(ctx, pin_val);
96
+
97
+ int level = 0;
98
+ if (ok) ok = mik__read_level(ctx, gpio, "level", &level);
99
+ JS_FreeValue(ctx, gpio);
100
+ if (!ok) return false;
101
+
102
+ gpio_int_type_t intr = (level == 1) ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
103
+ esp_err_t err = gpio_wakeup_enable(static_cast<gpio_num_t>(pin), intr);
104
+ if (err != ESP_OK) {
105
+ JS_ThrowInternalError(ctx, "GPIO wakeup on pin %d failed: %s", pin, esp_err_to_name(err));
106
+ return false;
107
+ }
108
+ err = esp_sleep_enable_gpio_wakeup();
109
+ if (err != ESP_OK) {
110
+ JS_ThrowInternalError(ctx, "GPIO wakeup source failed: %s", esp_err_to_name(err));
111
+ return false;
112
+ }
113
+ return true;
114
+ }
115
+
116
+ static bool mik__configure_ext0(JSContext* ctx, JSValue sources) {
117
+ JSValue ext0 = JS_GetPropertyStr(ctx, sources, "ext0");
118
+ if (JS_IsUndefined(ext0)) {
119
+ JS_FreeValue(ctx, ext0);
120
+ return true;
121
+ }
122
+ #if SOC_PM_SUPPORT_EXT0_WAKEUP
123
+ JSValue pin_val = JS_GetPropertyStr(ctx, ext0, "pin");
124
+ int32_t pin;
125
+ bool ok = !JS_ToInt32(ctx, &pin, pin_val);
126
+ JS_FreeValue(ctx, pin_val);
127
+
128
+ int level = 0;
129
+ if (ok) ok = mik__read_level(ctx, ext0, "level", &level);
130
+ JS_FreeValue(ctx, ext0);
131
+ if (!ok) return false;
132
+
133
+ mik__rtc_pull_for_wake(pin, level == 1);
134
+ esp_err_t err = esp_sleep_enable_ext0_wakeup(static_cast<gpio_num_t>(pin), level);
135
+ if (err != ESP_OK) {
136
+ JS_ThrowInternalError(ctx, "EXT0 wakeup on pin %d failed: %s", pin, esp_err_to_name(err));
137
+ return false;
138
+ }
139
+ return true;
140
+ #else
141
+ JS_FreeValue(ctx, ext0);
142
+ JS_ThrowInternalError(ctx, "EXT0 wakeup is not supported on this chip");
143
+ return false;
144
+ #endif
145
+ }
146
+
147
+ static bool mik__configure_ext1(JSContext* ctx, JSValue sources) {
148
+ JSValue ext1 = JS_GetPropertyStr(ctx, sources, "ext1");
149
+ if (JS_IsUndefined(ext1)) {
150
+ JS_FreeValue(ctx, ext1);
151
+ return true;
152
+ }
153
+ #if SOC_PM_SUPPORT_EXT1_WAKEUP
154
+ JSValue pins_val = JS_GetPropertyStr(ctx, ext1, "pins");
155
+ JSValue len_val = JS_GetPropertyStr(ctx, pins_val, "length");
156
+ uint32_t pin_count;
157
+ bool ok = !JS_ToUint32(ctx, &pin_count, len_val);
158
+ JS_FreeValue(ctx, len_val);
159
+
160
+ uint64_t pin_mask = 0;
161
+ for (uint32_t i = 0; ok && i < pin_count; i++) {
162
+ JSValue pin_val = JS_GetPropertyUint32(ctx, pins_val, i);
163
+ int32_t pin;
164
+ if (JS_ToInt32(ctx, &pin, pin_val))
165
+ ok = false;
166
+ else
167
+ pin_mask |= (1ULL << pin);
168
+ JS_FreeValue(ctx, pin_val);
169
+ }
170
+ JS_FreeValue(ctx, pins_val);
171
+
172
+ int mode_value = 0;
173
+ if (ok) {
174
+ JSValue mode_val = JS_GetPropertyStr(ctx, ext1, "mode");
175
+ const char* mode_str = JS_ToCString(ctx, mode_val);
176
+ JS_FreeValue(ctx, mode_val);
177
+ if (!mode_str) {
178
+ ok = false;
179
+ } else {
180
+ if (strcmp(mode_str, "any-low") == 0) {
181
+ // ESP32's EXT1 only ships ALL_LOW (wake when every selected pin
182
+ // is low); newer chips ship ANY_LOW. The two are equivalent for
183
+ // a single-pin mask, so we accept that case on ESP32 and route
184
+ // it through ALL_LOW. Multi-pin "any-low" can't be honored on
185
+ // ESP32 — the hardware always requires every pin to be low —
186
+ // so we reject it loudly rather than silently changing the
187
+ // wake condition.
188
+ #if CONFIG_IDF_TARGET_ESP32
189
+ if (pin_count > 1) {
190
+ JS_ThrowTypeError(
191
+ ctx,
192
+ "'any-low' with multiple pins is not supported on ESP32: "
193
+ "hardware can only wake when all selected pins are low. "
194
+ "Use one pin per ext1 source, or use 'any-high' mode.");
195
+ ok = false;
196
+ } else {
197
+ mode_value = ESP_EXT1_WAKEUP_ALL_LOW;
198
+ }
199
+ #else
200
+ mode_value = ESP_EXT1_WAKEUP_ANY_LOW;
201
+ #endif
202
+ } else if (strcmp(mode_str, "any-high") == 0) {
203
+ mode_value = ESP_EXT1_WAKEUP_ANY_HIGH;
204
+ } else {
205
+ JS_ThrowRangeError(ctx, "ext1.mode must be 'any-low' or 'any-high'");
206
+ ok = false;
207
+ }
208
+ JS_FreeCString(ctx, mode_str);
209
+ }
210
+ }
211
+ JS_FreeValue(ctx, ext1);
212
+ if (!ok) return false;
213
+
214
+ const bool wake_on_high = mode_value == ESP_EXT1_WAKEUP_ANY_HIGH;
215
+ for (int pin = 0; pin < 64; pin++) {
216
+ if (pin_mask & (1ULL << pin)) {
217
+ mik__rtc_pull_for_wake(pin, wake_on_high);
218
+ }
219
+ }
220
+ esp_err_t err = esp_sleep_enable_ext1_wakeup(
221
+ pin_mask, static_cast<esp_sleep_ext1_wakeup_mode_t>(mode_value));
222
+ if (err != ESP_OK) {
223
+ JS_ThrowInternalError(ctx, "EXT1 wakeup failed: %s", esp_err_to_name(err));
224
+ return false;
225
+ }
226
+ return true;
227
+ #else
228
+ JS_FreeValue(ctx, ext1);
229
+ JS_ThrowInternalError(ctx, "EXT1 wakeup is not supported on this chip");
230
+ return false;
231
+ #endif
232
+ }
233
+
234
+ /* Clears any previously-configured sources, then applies the ones in
235
+ * `sources`. Throws and returns false on any error. */
236
+ static bool mik__configure_wakeup_sources(JSContext* ctx, JSValue sources) {
237
+ if (!JS_IsObject(sources)) {
238
+ JS_ThrowTypeError(ctx, "wakeup sources must be an object");
239
+ return false;
240
+ }
241
+ esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
242
+ if (!mik__configure_timer(ctx, sources)) return false;
243
+ if (!mik__configure_gpio(ctx, sources)) return false;
244
+ if (!mik__configure_ext0(ctx, sources)) return false;
245
+ if (!mik__configure_ext1(ctx, sources)) return false;
246
+ return true;
247
+ }
248
+
18
249
  /* ── native:sleep JS module ─────────────────────────────────────────── */
19
250
 
20
251
  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;
252
+ if (!mik__configure_wakeup_sources(ctx, argv[0])) return JS_EXCEPTION;
23
253
 
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
- }
254
+ /* Flush + close the file log so the buffered line buffer and stdio
255
+ * buffer make it to flash; deep sleep reboots the chip otherwise. */
256
+ mik_logfile_close();
29
257
 
30
258
  /* Note: we intentionally do NOT call MIK_FreeRuntime() here. We are inside
31
259
  * a JS function call, so the runtime still has live GC objects on the call
@@ -37,23 +265,19 @@ static JSValue mik__sleep_deep(JSContext* ctx, JSValue this_val, int argc, JSVal
37
265
  }
38
266
 
39
267
  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
- }
268
+ if (!mik__configure_wakeup_sources(ctx, argv[0])) return JS_EXCEPTION;
50
269
 
270
+ /* Tear down USB before sleeping so the host sees a clean unplug,
271
+ * not a silent enumerated-but-asleep device. This lets `mikro dev`
272
+ * trigger its existing reconnect path. Restored after wake. */
273
+ mik__serial_io_detach_usb();
51
274
  esp_err_t err = esp_light_sleep_start();
275
+ mik__serial_io_attach_usb();
276
+
52
277
  if (err != ESP_OK)
53
- return mik__result_err_named(ctx, "LightSleepFailed",
54
- "light sleep failed: %s", esp_err_to_name(err));
278
+ return JS_ThrowInternalError(ctx, "light sleep failed: %s", esp_err_to_name(err));
55
279
 
56
- return mik__result_ok_void(ctx);
280
+ return JS_UNDEFINED;
57
281
  }
58
282
 
59
283
  static JSValue mik__sleep_get_wakeup_cause(JSContext* ctx, JSValue this_val, int argc,
@@ -62,126 +286,24 @@ static JSValue mik__sleep_get_wakeup_cause(JSContext* ctx, JSValue this_val, int
62
286
  return JS_NewString(ctx, mik__wakeup_cause_str(causes));
63
287
  }
64
288
 
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,
289
+ static JSValue mik__sleep_can_wake_from_ext0(JSContext* ctx, JSValue this_val, int argc,
79
290
  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
291
  #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);
292
+ return JS_TRUE;
118
293
  #else
119
- return mik__result_err_named(ctx, "WakeupConfigFailed",
120
- "EXT0 wakeup is not supported on this chip");
294
+ return JS_FALSE;
121
295
  #endif
122
296
  }
123
297
 
124
- static JSValue mik__sleep_enable_ext1_wakeup(JSContext* ctx, JSValue this_val, int argc,
125
- JSValue* argv) {
298
+ static JSValue mik__sleep_can_wake_from_ext1(JSContext* ctx, JSValue this_val, int argc,
299
+ JSValue* argv) {
126
300
  #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);
301
+ return JS_TRUE;
139
302
  #else
140
- return mik__result_err_named(ctx, "WakeupConfigFailed",
141
- "EXT1 wakeup is not supported on this chip");
303
+ return JS_FALSE;
142
304
  #endif
143
305
  }
144
306
 
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
307
  /* ── Module init ─────────────────────────────────────────────────── */
186
308
 
187
309
  static int mik__sleep_module_init(JSContext* ctx, JSModuleDef* m) {
@@ -192,20 +314,11 @@ static int mik__sleep_module_init(JSContext* ctx, JSModuleDef* m) {
192
314
  JS_SetModuleExport(ctx, m, "getWakeupCause",
193
315
  JS_NewCFunction(ctx, mik__sleep_get_wakeup_cause, "getWakeupCause", 0));
194
316
  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));
317
+ ctx, m, "canWakeFromExt0",
318
+ JS_NewCFunction(ctx, mik__sleep_can_wake_from_ext0, "canWakeFromExt0", 0));
206
319
  JS_SetModuleExport(
207
- ctx, m, "disableWakeupSource",
208
- JS_NewCFunction(ctx, mik__sleep_disable_wakeup_source, "disableWakeupSource", 1));
320
+ ctx, m, "canWakeFromExt1",
321
+ JS_NewCFunction(ctx, mik__sleep_can_wake_from_ext1, "canWakeFromExt1", 0));
209
322
  return 0;
210
323
  }
211
324
 
@@ -215,11 +328,8 @@ static JSModuleDef* mik__sleep_init(JSContext* ctx) {
215
328
  JS_AddModuleExport(ctx, m, "deepSleep");
216
329
  JS_AddModuleExport(ctx, m, "lightSleep");
217
330
  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");
331
+ JS_AddModuleExport(ctx, m, "canWakeFromExt0");
332
+ JS_AddModuleExport(ctx, m, "canWakeFromExt1");
223
333
  return m;
224
334
  }
225
335
 
@@ -31,26 +31,17 @@ TEST_CASE("native:sleep exports expected functions", "[modules]") {
31
31
  setup();
32
32
 
33
33
  JSValue ret = eval_module(R"(
34
- import {
35
- deepSleep, lightSleep, getWakeupCause,
36
- enableTimerWakeup, enableGpioWakeup,
37
- disableWakeupSource
38
- } from "native:sleep";
34
+ import { deepSleep, lightSleep, getWakeupCause } from "native:sleep";
39
35
  globalThis.__deepSleep = typeof deepSleep === "function";
40
36
  globalThis.__lightSleep = typeof lightSleep === "function";
41
37
  globalThis.__getWakeupCause = typeof getWakeupCause === "function";
42
- globalThis.__enableTimerWakeup = typeof enableTimerWakeup === "function";
43
- globalThis.__enableGpioWakeup = typeof enableGpioWakeup === "function";
44
- globalThis.__disableWakeupSource = typeof disableWakeupSource === "function";
45
38
  )");
46
39
  TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
47
40
 
48
41
  JSValue global = JS_GetGlobalObject(ctx);
49
42
 
50
- const char* names[] = {"__deepSleep", "__lightSleep", "__getWakeupCause",
51
- "__enableTimerWakeup", "__enableGpioWakeup",
52
- "__disableWakeupSource"};
53
- for (int i = 0; i < 6; i++) {
43
+ const char* names[] = {"__deepSleep", "__lightSleep", "__getWakeupCause"};
44
+ for (int i = 0; i < 3; i++) {
54
45
  JSValue v = JS_GetPropertyStr(ctx, global, names[i]);
55
46
  TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, v), names[i]);
56
47
  JS_FreeValue(ctx, v);
@@ -92,77 +83,15 @@ TEST_CASE("native:sleep getWakeupCause returns a string", "[modules]") {
92
83
  teardown();
93
84
  }
94
85
 
95
- /* ── enableTimerWakeup accepts microseconds ──────────────────────── */
86
+ /* ── invalid gpio.level throws RangeError ────────────────────────── */
96
87
 
97
- TEST_CASE("native:sleep enableTimerWakeup does not throw", "[modules]") {
88
+ TEST_CASE("native:sleep lightSleep throws RangeError on invalid level", "[modules]") {
98
89
  setup();
99
90
 
100
91
  JSValue ret = eval_module(R"(
101
- import { enableTimerWakeup } from "native:sleep";
102
- enableTimerWakeup(5000000);
103
- globalThis.__ok = true;
104
- )");
105
- TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
106
-
107
- JSValue global = JS_GetGlobalObject(ctx);
108
- JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
109
- TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "enableTimerWakeup should succeed");
110
- JS_FreeValue(ctx, ok);
111
- JS_FreeValue(ctx, global);
112
- teardown();
113
- }
114
-
115
- /* ── disableWakeupSource works ───────────────────────────────────── */
116
-
117
- TEST_CASE("native:sleep disableWakeupSource clears all sources", "[modules]") {
118
- setup();
119
-
120
- JSValue ret = eval_module(R"(
121
- import { enableTimerWakeup, disableWakeupSource } from "native:sleep";
122
- enableTimerWakeup(1000000);
123
- disableWakeupSource();
124
- globalThis.__ok = true;
125
- )");
126
- TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
127
-
128
- JSValue global = JS_GetGlobalObject(ctx);
129
- JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
130
- TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "disableWakeupSource should succeed");
131
- JS_FreeValue(ctx, ok);
132
- JS_FreeValue(ctx, global);
133
- teardown();
134
- }
135
-
136
- /* ── disableWakeupSource with specific source ────────────────────── */
137
-
138
- TEST_CASE("native:sleep disableWakeupSource with 'timer'", "[modules]") {
139
- setup();
140
-
141
- JSValue ret = eval_module(R"(
142
- import { enableTimerWakeup, disableWakeupSource } from "native:sleep";
143
- enableTimerWakeup(1000000);
144
- disableWakeupSource("timer");
145
- globalThis.__ok = true;
146
- )");
147
- TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
148
-
149
- JSValue global = JS_GetGlobalObject(ctx);
150
- JSValue ok = JS_GetPropertyStr(ctx, global, "__ok");
151
- TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, ok), "disableWakeupSource('timer') should succeed");
152
- JS_FreeValue(ctx, ok);
153
- JS_FreeValue(ctx, global);
154
- teardown();
155
- }
156
-
157
- /* ── disableWakeupSource throws on invalid source ────────────────── */
158
-
159
- TEST_CASE("native:sleep disableWakeupSource throws on invalid source", "[modules]") {
160
- setup();
161
-
162
- JSValue ret = eval_module(R"(
163
- import { disableWakeupSource } from "native:sleep";
92
+ import { lightSleep } from "native:sleep";
164
93
  try {
165
- disableWakeupSource("invalid");
94
+ lightSleep({gpio: {pin: 0, level: "bogus"}});
166
95
  globalThis.__threw = false;
167
96
  } catch (e) {
168
97
  globalThis.__threw = true;
@@ -174,8 +103,7 @@ TEST_CASE("native:sleep disableWakeupSource throws on invalid source", "[modules
174
103
  JSValue global = JS_GetGlobalObject(ctx);
175
104
 
176
105
  JSValue threw = JS_GetPropertyStr(ctx, global, "__threw");
177
- TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw),
178
- "disableWakeupSource should throw on invalid source");
106
+ TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, threw), "lightSleep should throw on invalid level");
179
107
  JS_FreeValue(ctx, threw);
180
108
 
181
109
  JSValue isRange = JS_GetPropertyStr(ctx, global, "__isRangeError");
@@ -190,19 +118,19 @@ TEST_CASE("native:sleep disableWakeupSource throws on invalid source", "[modules
190
118
  /* Light sleep disconnects UART console on targets without USB Serial/JTAG,
191
119
  causing the test monitor to stall. Only run on chips that have USB
192
120
  Serial/JTAG which keeps the connection alive through light sleep. */
193
- TEST_CASE("native:sleep lightSleep with 1ms returns or rejects gracefully", "[modules]") {
121
+ TEST_CASE("native:sleep lightSleep with 1us timer returns or rejects gracefully", "[modules]") {
194
122
  #if !SOC_USB_SERIAL_JTAG_SUPPORTED
195
- TEST_IGNORE_MESSAGE("light sleep disconnects UART console skipped on non-USB targets");
123
+ TEST_IGNORE_MESSAGE("light sleep disconnects UART console, skipped on non-USB targets");
196
124
  #endif
197
125
  setup();
198
126
 
199
127
  JSValue ret = eval_module(R"(
200
128
  import { lightSleep } from "native:sleep";
201
129
  try {
202
- lightSleep(1);
130
+ lightSleep({timer: 1});
203
131
  globalThis.__result = "ok";
204
132
  } catch (e) {
205
- // Light sleep may fail on boards with USB JTAG or other constraints
133
+ /* Light sleep may throw on boards with USB JTAG or other constraints */
206
134
  globalThis.__result = "caught:" + e.message;
207
135
  }
208
136
  )");
@@ -212,7 +140,7 @@ TEST_CASE("native:sleep lightSleep with 1ms returns or rejects gracefully", "[mo
212
140
  JSValue result = JS_GetPropertyStr(ctx, global, "__result");
213
141
  const char* str = JS_ToCString(ctx, result);
214
142
  TEST_ASSERT_NOT_NULL_MESSAGE(str, "result should be set");
215
- /* Either "ok" (sleep succeeded) or "caught:..." (threw a catchable error) — both are fine */
143
+ /* Either "ok" (sleep succeeded) or "caught:..." (threw a catchable error) */
216
144
  TEST_ASSERT_TRUE_MESSAGE(strncmp(str, "ok", 2) == 0 || strncmp(str, "caught:", 7) == 0,
217
145
  "lightSleep should either succeed or throw a catchable error");
218
146
  JS_FreeCString(ctx, str);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikrojs/firmware",
3
- "version": "0.8.0-pr-115.g87a99f9",
3
+ "version": "0.8.0",
4
4
  "description": "Mikro.js ESP32 firmware: ESP-IDF component, build tools, and project template",
5
5
  "keywords": [
6
6
  "esp-idf",
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "esbuild": "^0.28.0",
54
- "@mikrojs/native": "0.8.0-pr-115.g87a99f9",
55
- "@mikrojs/quickjs": "0.8.0-pr-115.g87a99f9"
54
+ "@mikrojs/quickjs": "0.8.0",
55
+ "@mikrojs/native": "0.8.0"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=24.0.0"
Binary file
Binary file
Binary file
Binary file
Binary file