@mikrojs/firmware 0.15.0 → 0.16.0-next.20260618225923

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.
@@ -477,12 +477,66 @@ static JSValue mik__wifi_connect(JSContext* ctx, JSValue this_val, int argc, JSV
477
477
  return mik__result_ok(ctx, promise);
478
478
  }
479
479
 
480
+ /* Fully tear down the WiFi driver so the heap used by the radio stack, beacon
481
+ * offsets, event handlers, and netif interfaces is returned to the pool. Shared
482
+ * by disconnect({shutdown:true}) and runtime teardown. Safe to call when the
483
+ * radio is already down. */
484
+ static void mik__wifi_radio_teardown(void) {
485
+ if (!s_wifi_initialized) return;
486
+ if (s_wifi_started) {
487
+ esp_wifi_stop();
488
+ s_wifi_started = false;
489
+ }
490
+ /* ensure_initialized registered these via esp_event_handler_instance_register
491
+ * with a nullptr context out-param, so we can't use instance_unregister.
492
+ * esp_event_handler_unregister matches on (base, id, handler fn) instead. */
493
+ esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, mik__wifi_event_handler);
494
+ esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, mik__wifi_event_handler);
495
+ esp_wifi_deinit();
496
+ if (s_sta_netif) {
497
+ esp_netif_destroy_default_wifi(s_sta_netif);
498
+ s_sta_netif = nullptr;
499
+ }
500
+ if (s_ap_netif) {
501
+ esp_netif_destroy_default_wifi(s_ap_netif);
502
+ s_ap_netif = nullptr;
503
+ }
504
+ s_wifi_initialized = false;
505
+ }
506
+
480
507
  static JSValue mik__wifi_disconnect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
508
+ bool shutdown = argc < 1 || JS_IsUndefined(argv[0]) || JS_ToBool(ctx, argv[0]);
509
+
481
510
  esp_err_t err = esp_wifi_disconnect();
482
- if (err != ESP_OK) {
511
+ /* When shutting down we proceed to teardown even if the radio was never
512
+ * started, so a plain disconnect() reliably powers things down. */
513
+ if (err != ESP_OK && !(shutdown && (err == ESP_ERR_WIFI_NOT_STARTED ||
514
+ err == ESP_ERR_WIFI_NOT_INIT))) {
483
515
  return mik__result_err_named(ctx, "DisconnectFailed",
484
516
  "Failed to disconnect from WiFi: %s", esp_err_to_name(err));
485
517
  }
518
+
519
+ if (shutdown) {
520
+ /* Teardown unregisters the event handlers, so a connect/scan in flight
521
+ * would never be settled by the event loop. Settle them now (with an
522
+ * error Result, matching the consume failure path) so the awaiting JS
523
+ * resolves deterministically instead of hanging until runtime teardown. */
524
+ MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
525
+ MIKWifiState* state = mik_rt ? mik__wifi_st(mik_rt) : nullptr;
526
+ if (state && state->connect_pending) {
527
+ JSValue result = mik__result_err_named(ctx, "ConnectFailed",
528
+ "WiFi disconnected before connecting");
529
+ MIK_ResolvePromise(ctx, &state->connect_promise, 1, &result);
530
+ state->connect_pending = false;
531
+ }
532
+ if (state && state->scan_pending) {
533
+ JSValue result = mik__result_err_named(ctx, "ScanFailed",
534
+ "WiFi shut down during scan");
535
+ MIK_ResolvePromise(ctx, &state->scan_promise, 1, &result);
536
+ state->scan_pending = false;
537
+ }
538
+ mik__wifi_radio_teardown();
539
+ }
486
540
  return mik__result_ok_void(ctx);
487
541
  }
488
542
 
@@ -1100,7 +1154,7 @@ static JSValue mik__wifi_ap_set_inactive_timeout(JSContext* ctx, JSValue this_va
1100
1154
 
1101
1155
  static const JSCFunctionListEntry mik__wifi_proto_funcs[] = {
1102
1156
  MIK_CFUNC_DEF("connect", 2, mik__wifi_connect),
1103
- MIK_CFUNC_DEF("disconnect", 0, mik__wifi_disconnect),
1157
+ MIK_CFUNC_DEF("disconnect", 1, mik__wifi_disconnect),
1104
1158
  MIK_CFUNC_DEF("rssi", 0, mik__wifi_rssi),
1105
1159
  MIK_CFUNC_DEF("ip", 0, mik__wifi_ip),
1106
1160
  MIK_CFUNC_DEF("status", 0, mik__wifi_status),
@@ -1410,35 +1464,11 @@ void mik__wifi_destroy(JSContext* ctx) {
1410
1464
  delete state;
1411
1465
  mik__wifi_st(mik_rt) = nullptr;
1412
1466
 
1413
- /* Fully tear down the WiFi driver so the heap used by the radio stack,
1414
- * beacon offsets, event handlers, and netif interfaces is returned to
1415
- * the pool. Without this, repeated runtime lifecycles (e.g. on-device
1416
- * test suites that create + destroy a runtime per test) eventually hit
1417
- * `alloc pm_beacon_offset fail` when the heap is too fragmented for
1418
- * even small internal allocations. */
1419
- if (s_wifi_initialized) {
1420
- if (s_wifi_started) {
1421
- esp_wifi_stop();
1422
- s_wifi_started = false;
1423
- }
1424
- /* ensure_initialized registered these via esp_event_handler_instance_register
1425
- * with a nullptr context out-param, so we can't use instance_unregister.
1426
- * esp_event_handler_unregister matches on (base, id, handler fn) instead. */
1427
- esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
1428
- mik__wifi_event_handler);
1429
- esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
1430
- mik__wifi_event_handler);
1431
- esp_wifi_deinit();
1432
- if (s_sta_netif) {
1433
- esp_netif_destroy_default_wifi(s_sta_netif);
1434
- s_sta_netif = nullptr;
1435
- }
1436
- if (s_ap_netif) {
1437
- esp_netif_destroy_default_wifi(s_ap_netif);
1438
- s_ap_netif = nullptr;
1439
- }
1440
- s_wifi_initialized = false;
1441
- }
1467
+ /* Return the radio heap to the pool. Without this, repeated runtime
1468
+ * lifecycles (e.g. on-device test suites that create + destroy a runtime
1469
+ * per test) eventually hit `alloc pm_beacon_offset fail` when the heap is
1470
+ * too fragmented for even small internal allocations. */
1471
+ mik__wifi_radio_teardown();
1442
1472
  }
1443
1473
 
1444
1474
  MIK_REGISTER_MODULE(wifi, "native:mikro/wifi", mik__wifi_init, mik__wifi_consume, mik__wifi_destroy)
@@ -151,13 +151,13 @@ TEST_CASE("Wifi connect returns a promise", "[wifi]") {
151
151
  setup();
152
152
 
153
153
  // Only test that connect() returns a Promise — don't actually wait for connection.
154
- // We call disconnect() right after to avoid leaving WiFi in "connecting" state.
154
+ // disconnect(false) keeps the radio up to avoid tearing it down between tests.
155
155
  JSValue ret = eval_module(R"(
156
156
  import { Wifi } from "native:mikro/wifi";
157
157
  const wifi = new Wifi();
158
158
  const result = wifi.connect("test", "pass");
159
159
  globalThis.__isPromise = result.ok && result.value instanceof Promise;
160
- wifi.disconnect();
160
+ wifi.disconnect(false);
161
161
  )");
162
162
  TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
163
163
 
@@ -169,6 +169,68 @@ TEST_CASE("Wifi connect returns a promise", "[wifi]") {
169
169
  teardown();
170
170
  }
171
171
 
172
+ /* ── Disconnect shutdown powers the radio down and stays reusable ──── */
173
+
174
+ TEST_CASE("Wifi disconnect({shutdown:true}) tears down and re-inits on reuse", "[wifi]") {
175
+ setup();
176
+
177
+ // disconnect(true) is the default: it powers the radio down. A subsequent
178
+ // call (here getTxPower, which lazily restarts the radio) must still work.
179
+ JSValue ret = eval_module(R"(
180
+ import { Wifi } from "native:mikro/wifi";
181
+ const wifi = new Wifi();
182
+ wifi.connect("test", "pass");
183
+ const down = wifi.disconnect(true);
184
+ const reuse = wifi.getTxPower();
185
+ globalThis.__downOk = down.ok;
186
+ globalThis.__reuseOk = reuse.ok;
187
+ )");
188
+ TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
189
+
190
+ JSValue global = JS_GetGlobalObject(ctx);
191
+ JSValue downOk = JS_GetPropertyStr(ctx, global, "__downOk");
192
+ JSValue reuseOk = JS_GetPropertyStr(ctx, global, "__reuseOk");
193
+ TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, downOk), "disconnect(true) should return ok");
194
+ TEST_ASSERT_TRUE_MESSAGE(JS_ToBool(ctx, reuseOk), "radio should re-init after shutdown");
195
+ JS_FreeValue(ctx, downOk);
196
+ JS_FreeValue(ctx, reuseOk);
197
+ JS_FreeValue(ctx, global);
198
+ teardown();
199
+ }
200
+
201
+ /* ── Disconnect shutdown settles a connect in flight ──────────────── */
202
+
203
+ TEST_CASE("Wifi disconnect({shutdown:true}) settles a pending connect", "[wifi]") {
204
+ setup();
205
+
206
+ // Teardown unregisters the event handlers, so the connect promise must be
207
+ // settled by disconnect() itself — otherwise the await would hang forever.
208
+ JSValue ret = eval_module(R"(
209
+ import { Wifi } from "native:mikro/wifi";
210
+ const wifi = new Wifi();
211
+ const result = wifi.connect("test", "pass");
212
+ globalThis.__settled = "pending";
213
+ if (result.ok) {
214
+ result.value.then((res) => {
215
+ globalThis.__settled = res.ok ? "ok" : res.error.name;
216
+ });
217
+ }
218
+ wifi.disconnect(true);
219
+ )");
220
+ TEST_ASSERT_FALSE_MESSAGE(JS_IsException(ret), "Module eval should not throw");
221
+
222
+ JSValue global = JS_GetGlobalObject(ctx);
223
+ JSValue settled = JS_GetPropertyStr(ctx, global, "__settled");
224
+ const char* settled_str = JS_ToCString(ctx, settled);
225
+ TEST_ASSERT_NOT_NULL(settled_str);
226
+ TEST_ASSERT_EQUAL_STRING_MESSAGE("ConnectFailed", settled_str,
227
+ "pending connect should settle as ConnectFailed, not hang");
228
+ JS_FreeCString(ctx, settled_str);
229
+ JS_FreeValue(ctx, settled);
230
+ JS_FreeValue(ctx, global);
231
+ teardown();
232
+ }
233
+
172
234
  /* ── Scan returns a promise ────────────────────────────────────────── */
173
235
 
174
236
  TEST_CASE("Wifi scan returns a promise", "[wifi]") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikrojs/firmware",
3
- "version": "0.15.0",
3
+ "version": "0.16.0-next.20260618225923",
4
4
  "description": "Mikro.js ESP32 firmware: ESP-IDF component, build tools, and project template",
5
5
  "keywords": [
6
6
  "esp-idf",
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "esbuild": "^0.28.0",
50
- "@mikrojs/native": "0.15.0",
51
- "@mikrojs/quickjs": "0.15.0"
50
+ "@mikrojs/native": "0.16.0-next.20260618225923+79c8761",
51
+ "@mikrojs/quickjs": "0.16.0-next.20260618225923+79c8761"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=24.0.0"
Binary file
Binary file
Binary file
Binary file
Binary file