@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.
- package/components/mikrojs/mik_wifi.cpp +61 -31
- package/components/mikrojs/test/wifi_test.cpp +64 -2
- package/package.json +3 -3
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c5/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c5/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
|
@@ -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
|
|
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",
|
|
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
|
-
/*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
51
|
-
"@mikrojs/quickjs": "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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|