@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.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/bin/idf.py +7 -0
- package/chips.json +3 -0
- package/cmake.js +9 -0
- package/components/mikrojs/CMakeLists.txt +187 -0
- package/components/mikrojs/Kconfig +55 -0
- package/components/mikrojs/idf_component.yml +6 -0
- package/components/mikrojs/include/mem.h +3 -0
- package/components/mikrojs/include/mik_color.h +3 -0
- package/components/mikrojs/include/mik_http_internal.h +77 -0
- package/components/mikrojs/include/mikrojs.h +5 -0
- package/components/mikrojs/include/mikrojs_esp32.h +65 -0
- package/components/mikrojs/include/private.h +10 -0
- package/components/mikrojs/include/utils.h +3 -0
- package/components/mikrojs/mik_ble.cpp +1588 -0
- package/components/mikrojs/mik_ble_c_shim.c +61 -0
- package/components/mikrojs/mik_ble_c_shim.h +37 -0
- package/components/mikrojs/mik_config.cpp +167 -0
- package/components/mikrojs/mik_deploy.cpp +584 -0
- package/components/mikrojs/mik_http.cpp +916 -0
- package/components/mikrojs/mik_i2c.cpp +364 -0
- package/components/mikrojs/mik_main.cpp +542 -0
- package/components/mikrojs/mik_neopixel.cpp +437 -0
- package/components/mikrojs/mik_nvs_kv.cpp +219 -0
- package/components/mikrojs/mik_pin.cpp +195 -0
- package/components/mikrojs/mik_pwm.cpp +525 -0
- package/components/mikrojs/mik_recovery.cpp +86 -0
- package/components/mikrojs/mik_rtc.cpp +305 -0
- package/components/mikrojs/mik_serial_io.cpp +362 -0
- package/components/mikrojs/mik_sleep.cpp +226 -0
- package/components/mikrojs/mik_sntp.cpp +275 -0
- package/components/mikrojs/mik_spi.cpp +330 -0
- package/components/mikrojs/mik_uart.cpp +497 -0
- package/components/mikrojs/mik_wifi.cpp +1434 -0
- package/components/mikrojs/platform_esp32.cpp +192 -0
- package/components/mikrojs/test/CMakeLists.txt +32 -0
- package/components/mikrojs/test/abort_test.cpp +254 -0
- package/components/mikrojs/test/ble_test.cpp +714 -0
- package/components/mikrojs/test/fs_js_test.cpp +458 -0
- package/components/mikrojs/test/fs_pub_test.cpp +312 -0
- package/components/mikrojs/test/http_test.cpp +475 -0
- package/components/mikrojs/test/i2c_test.cpp +138 -0
- package/components/mikrojs/test/modules_extended_test.cpp +137 -0
- package/components/mikrojs/test/modules_test.cpp +131 -0
- package/components/mikrojs/test/pins_test.cpp +47 -0
- package/components/mikrojs/test/pwm_test.cpp +166 -0
- package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
- package/components/mikrojs/test/rtc_test.cpp +331 -0
- package/components/mikrojs/test/runtime_test.cpp +89 -0
- package/components/mikrojs/test/sleep_test.cpp +222 -0
- package/components/mikrojs/test/sntp_test.cpp +249 -0
- package/components/mikrojs/test/stdio_test.cpp +449 -0
- package/components/mikrojs/test/sys_test.cpp +165 -0
- package/components/mikrojs/test/text_encoding_test.cpp +224 -0
- package/components/mikrojs/test/timers_js_test.cpp +244 -0
- package/components/mikrojs/test/timers_test.cpp +79 -0
- package/components/mikrojs/test/wifi_test.cpp +599 -0
- package/default-app/main/CMakeLists.txt +3 -0
- package/default-app/main/main.cpp +5 -0
- package/discover.js +77 -0
- package/index.d.ts +7 -0
- package/index.js +20 -0
- package/package.json +61 -0
- package/partitions.csv +5 -0
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/flasher_args.json +24 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/flasher_args.json +24 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/flasher_args.json +24 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/flasher_args.json +24 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
- package/project.cmake +101 -0
- package/resolve.js +54 -0
- package/sdkconfig.defaults +127 -0
- package/sdkconfig.defaults.esp32 +8 -0
- package/sdkconfig.defaults.esp32c3 +15 -0
- package/sdkconfig.defaults.esp32c6 +26 -0
- package/sdkconfig.defaults.esp32s3 +22 -0
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
#include <cstring>
|
|
2
|
+
#include <vector>
|
|
3
|
+
|
|
4
|
+
#include "esp_event.h"
|
|
5
|
+
#include "esp_log.h"
|
|
6
|
+
#include "esp_mac.h"
|
|
7
|
+
#include "esp_netif.h"
|
|
8
|
+
#include "esp_wifi.h"
|
|
9
|
+
#include "freertos/FreeRTOS.h"
|
|
10
|
+
#include "freertos/queue.h"
|
|
11
|
+
#include "nvs_flash.h"
|
|
12
|
+
#include "private.h"
|
|
13
|
+
#include "utils.h"
|
|
14
|
+
|
|
15
|
+
/* Dynamic module data slot, allocated on first import */
|
|
16
|
+
static int mik__wifi_slot = -1;
|
|
17
|
+
|
|
18
|
+
/* Helper to access WiFi state from runtime module_data slot */
|
|
19
|
+
static inline MIKWifiState*& mik__wifi_st(MIKRuntime* rt) {
|
|
20
|
+
return reinterpret_cast<MIKWifiState*&>(rt->module_data[mik__wifi_slot]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#define MIK_WIFI_TAG "native:wifi"
|
|
24
|
+
#define MIK_WIFI_MAX_SCAN_RESULTS 20
|
|
25
|
+
#define MIK_WIFI_MAX_AP_STATIONS 4
|
|
26
|
+
|
|
27
|
+
// Create a JS string from raw SSID bytes, replacing invalid UTF-8 sequences with U+FFFD.
|
|
28
|
+
// SSIDs are up to 32 bytes of arbitrary data per IEEE 802.11; they are not required to be UTF-8.
|
|
29
|
+
static JSValue mik__new_string_from_ssid(JSContext* ctx, const uint8_t* ssid, size_t max_len) {
|
|
30
|
+
size_t len = strnlen(reinterpret_cast<const char*>(ssid), max_len);
|
|
31
|
+
// Worst case: every byte is invalid and replaced with 3-byte U+FFFD
|
|
32
|
+
char buf[32 * 3 + 1];
|
|
33
|
+
size_t out = 0;
|
|
34
|
+
|
|
35
|
+
for (size_t i = 0; i < len;) {
|
|
36
|
+
uint8_t b = ssid[i];
|
|
37
|
+
size_t seq_len = 0;
|
|
38
|
+
|
|
39
|
+
if (b < 0x80) {
|
|
40
|
+
seq_len = 1;
|
|
41
|
+
} else if ((b & 0xE0) == 0xC0) {
|
|
42
|
+
seq_len = 2;
|
|
43
|
+
} else if ((b & 0xF0) == 0xE0) {
|
|
44
|
+
seq_len = 3;
|
|
45
|
+
} else if ((b & 0xF8) == 0xF0) {
|
|
46
|
+
seq_len = 4;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bool valid = seq_len > 0 && i + seq_len <= len;
|
|
50
|
+
if (valid) {
|
|
51
|
+
// Verify continuation bytes
|
|
52
|
+
for (size_t j = 1; j < seq_len; j++) {
|
|
53
|
+
if ((ssid[i + j] & 0xC0) != 0x80) {
|
|
54
|
+
valid = false;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Reject overlong encodings and surrogates
|
|
60
|
+
if (valid && seq_len == 2 && b < 0xC2) valid = false;
|
|
61
|
+
if (valid && seq_len == 3) {
|
|
62
|
+
uint32_t cp = ((b & 0x0F) << 12) | ((ssid[i + 1] & 0x3F) << 6) | (ssid[i + 2] & 0x3F);
|
|
63
|
+
if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) valid = false;
|
|
64
|
+
}
|
|
65
|
+
if (valid && seq_len == 4) {
|
|
66
|
+
uint32_t cp = ((b & 0x07) << 18) | ((ssid[i + 1] & 0x3F) << 12) |
|
|
67
|
+
((ssid[i + 2] & 0x3F) << 6) | (ssid[i + 3] & 0x3F);
|
|
68
|
+
if (cp < 0x10000 || cp > 0x10FFFF) valid = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (valid) {
|
|
72
|
+
memcpy(buf + out, ssid + i, seq_len);
|
|
73
|
+
out += seq_len;
|
|
74
|
+
i += seq_len;
|
|
75
|
+
} else {
|
|
76
|
+
// U+FFFD replacement character (0xEF 0xBF 0xBD)
|
|
77
|
+
buf[out++] = '\xEF';
|
|
78
|
+
buf[out++] = '\xBF';
|
|
79
|
+
buf[out++] = '\xBD';
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return JS_NewStringLen(ctx, buf, out);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ── WiFi status codes (must match JS WifiStatusCodes) ─────────────── */
|
|
88
|
+
enum MIKWifiStatus : uint8_t {
|
|
89
|
+
MIK_WIFI_IDLE = 0,
|
|
90
|
+
MIK_WIFI_NO_SSID_AVAIL = 1,
|
|
91
|
+
MIK_WIFI_SCAN_COMPLETED = 2,
|
|
92
|
+
MIK_WIFI_CONNECTED = 3,
|
|
93
|
+
MIK_WIFI_CONNECT_FAILED = 4,
|
|
94
|
+
MIK_WIFI_CONNECTION_LOST = 5,
|
|
95
|
+
MIK_WIFI_DISCONNECTED = 6,
|
|
96
|
+
MIK_WIFI_STOPPED = 254,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/* ── WiFi event types for the FreeRTOS queue ───────────────────────── */
|
|
100
|
+
enum MIKWifiEventType : uint8_t {
|
|
101
|
+
MIK_WIFI_EVT_STATUS_CHANGE = 0,
|
|
102
|
+
MIK_WIFI_EVT_GOT_IP = 1,
|
|
103
|
+
MIK_WIFI_EVT_SCAN_DONE = 2,
|
|
104
|
+
MIK_WIFI_EVT_AP_STA_CONNECTED = 3,
|
|
105
|
+
MIK_WIFI_EVT_AP_STA_DISCONNECTED = 4,
|
|
106
|
+
MIK_WIFI_EVT_RSSI_LOW = 5,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
struct MIKWifiEvent {
|
|
110
|
+
MIKWifiEventType type;
|
|
111
|
+
MIKWifiStatus status;
|
|
112
|
+
union {
|
|
113
|
+
esp_netif_ip_info_t ip_info; // GOT_IP
|
|
114
|
+
uint8_t mac[6]; // AP_STA_CONNECTED / AP_STA_DISCONNECTED
|
|
115
|
+
int32_t rssi; // RSSI_LOW
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/* ── WiFi state (attached to MIKRuntime) ───────────────────────────── */
|
|
120
|
+
|
|
121
|
+
struct MIKWifiState {
|
|
122
|
+
QueueHandle_t event_queue;
|
|
123
|
+
MIKPromise connect_promise;
|
|
124
|
+
bool connect_pending;
|
|
125
|
+
MIKPromise scan_promise;
|
|
126
|
+
bool scan_pending;
|
|
127
|
+
// JS event listeners
|
|
128
|
+
std::vector<JSValue> on_connect;
|
|
129
|
+
std::vector<JSValue> on_disconnect;
|
|
130
|
+
std::vector<JSValue> on_ap_sta_connect;
|
|
131
|
+
std::vector<JSValue> on_ap_sta_disconnect;
|
|
132
|
+
std::vector<JSValue> on_rssi_low;
|
|
133
|
+
int32_t rssi_threshold;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/* ── Global WiFi state (singleton — only one STA on ESP32) ─────────── */
|
|
137
|
+
|
|
138
|
+
static bool s_wifi_initialized = false;
|
|
139
|
+
static bool s_wifi_started = false;
|
|
140
|
+
static bool s_country_configured = false;
|
|
141
|
+
static esp_netif_t* s_sta_netif = nullptr;
|
|
142
|
+
static esp_netif_t* s_ap_netif = nullptr;
|
|
143
|
+
static volatile MIKWifiStatus s_wifi_status = MIK_WIFI_STOPPED;
|
|
144
|
+
static QueueHandle_t s_event_queue = nullptr;
|
|
145
|
+
|
|
146
|
+
static void mik__format_mac(char* buf, size_t buf_len, const uint8_t* mac) {
|
|
147
|
+
snprintf(buf, buf_len, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
|
|
148
|
+
mac[5]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static bool mik__parse_mac(const char* str, uint8_t* mac) {
|
|
152
|
+
unsigned int m[6];
|
|
153
|
+
if (sscanf(str, "%02X:%02X:%02X:%02X:%02X:%02X", &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) !=
|
|
154
|
+
6) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
for (int i = 0; i < 6; i++) {
|
|
158
|
+
mac[i] = static_cast<uint8_t>(m[i]);
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static void mik__wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id,
|
|
164
|
+
void* event_data) {
|
|
165
|
+
MIKWifiEvent evt = {};
|
|
166
|
+
|
|
167
|
+
if (event_base == WIFI_EVENT) {
|
|
168
|
+
switch (event_id) {
|
|
169
|
+
case WIFI_EVENT_STA_START:
|
|
170
|
+
s_wifi_status = MIK_WIFI_IDLE;
|
|
171
|
+
evt.type = MIK_WIFI_EVT_STATUS_CHANGE;
|
|
172
|
+
evt.status = MIK_WIFI_IDLE;
|
|
173
|
+
break;
|
|
174
|
+
case WIFI_EVENT_STA_STOP:
|
|
175
|
+
s_wifi_status = MIK_WIFI_STOPPED;
|
|
176
|
+
evt.type = MIK_WIFI_EVT_STATUS_CHANGE;
|
|
177
|
+
evt.status = MIK_WIFI_STOPPED;
|
|
178
|
+
break;
|
|
179
|
+
case WIFI_EVENT_STA_CONNECTED:
|
|
180
|
+
s_wifi_status = MIK_WIFI_CONNECTED;
|
|
181
|
+
evt.type = MIK_WIFI_EVT_STATUS_CHANGE;
|
|
182
|
+
evt.status = MIK_WIFI_CONNECTED;
|
|
183
|
+
break;
|
|
184
|
+
case WIFI_EVENT_STA_DISCONNECTED: {
|
|
185
|
+
auto* info = static_cast<wifi_event_sta_disconnected_t*>(event_data);
|
|
186
|
+
evt.type = MIK_WIFI_EVT_STATUS_CHANGE;
|
|
187
|
+
switch (info->reason) {
|
|
188
|
+
case WIFI_REASON_NO_AP_FOUND:
|
|
189
|
+
case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD:
|
|
190
|
+
case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD:
|
|
191
|
+
s_wifi_status = MIK_WIFI_NO_SSID_AVAIL;
|
|
192
|
+
evt.status = MIK_WIFI_NO_SSID_AVAIL;
|
|
193
|
+
break;
|
|
194
|
+
case WIFI_REASON_AUTH_FAIL:
|
|
195
|
+
case WIFI_REASON_HANDSHAKE_TIMEOUT:
|
|
196
|
+
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
|
|
197
|
+
case WIFI_REASON_CONNECTION_FAIL:
|
|
198
|
+
s_wifi_status = MIK_WIFI_CONNECT_FAILED;
|
|
199
|
+
evt.status = MIK_WIFI_CONNECT_FAILED;
|
|
200
|
+
break;
|
|
201
|
+
case WIFI_REASON_ASSOC_LEAVE:
|
|
202
|
+
case WIFI_REASON_BEACON_TIMEOUT:
|
|
203
|
+
s_wifi_status = MIK_WIFI_CONNECTION_LOST;
|
|
204
|
+
evt.status = MIK_WIFI_CONNECTION_LOST;
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
s_wifi_status = MIK_WIFI_DISCONNECTED;
|
|
208
|
+
evt.status = MIK_WIFI_DISCONNECTED;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case WIFI_EVENT_SCAN_DONE:
|
|
214
|
+
s_wifi_status = MIK_WIFI_SCAN_COMPLETED;
|
|
215
|
+
evt.type = MIK_WIFI_EVT_SCAN_DONE;
|
|
216
|
+
evt.status = MIK_WIFI_SCAN_COMPLETED;
|
|
217
|
+
break;
|
|
218
|
+
case WIFI_EVENT_AP_STACONNECTED: {
|
|
219
|
+
auto* info = static_cast<wifi_event_ap_staconnected_t*>(event_data);
|
|
220
|
+
evt.type = MIK_WIFI_EVT_AP_STA_CONNECTED;
|
|
221
|
+
memcpy(evt.mac, info->mac, 6);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case WIFI_EVENT_AP_STADISCONNECTED: {
|
|
225
|
+
auto* info = static_cast<wifi_event_ap_stadisconnected_t*>(event_data);
|
|
226
|
+
evt.type = MIK_WIFI_EVT_AP_STA_DISCONNECTED;
|
|
227
|
+
memcpy(evt.mac, info->mac, 6);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case WIFI_EVENT_STA_BSS_RSSI_LOW: {
|
|
231
|
+
auto* info = static_cast<wifi_event_bss_rssi_low_t*>(event_data);
|
|
232
|
+
evt.type = MIK_WIFI_EVT_RSSI_LOW;
|
|
233
|
+
evt.rssi = info->rssi;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
default:
|
|
237
|
+
return; // no event to post
|
|
238
|
+
}
|
|
239
|
+
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
240
|
+
s_wifi_status = MIK_WIFI_CONNECTED;
|
|
241
|
+
evt.type = MIK_WIFI_EVT_GOT_IP;
|
|
242
|
+
evt.status = MIK_WIFI_CONNECTED;
|
|
243
|
+
auto* ip_event = static_cast<ip_event_got_ip_t*>(event_data);
|
|
244
|
+
evt.ip_info = ip_event->ip_info;
|
|
245
|
+
} else {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (s_event_queue) {
|
|
250
|
+
xQueueSend(s_event_queue, &evt, 0);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
esp_err_t mik__wifi_ensure_initialized() {
|
|
255
|
+
if (s_wifi_initialized) return ESP_OK;
|
|
256
|
+
|
|
257
|
+
esp_err_t err = nvs_flash_init();
|
|
258
|
+
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
259
|
+
nvs_flash_erase();
|
|
260
|
+
err = nvs_flash_init();
|
|
261
|
+
}
|
|
262
|
+
if (err != ESP_OK) return err;
|
|
263
|
+
|
|
264
|
+
err = esp_netif_init();
|
|
265
|
+
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) return err;
|
|
266
|
+
|
|
267
|
+
err = esp_event_loop_create_default();
|
|
268
|
+
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) return err;
|
|
269
|
+
|
|
270
|
+
/* If a previous attempt created the netif but later steps failed,
|
|
271
|
+
* reuse the existing one instead of creating a second default and
|
|
272
|
+
* asserting in esp_netif_create_default_wifi_sta. */
|
|
273
|
+
if (!s_sta_netif) {
|
|
274
|
+
s_sta_netif = esp_netif_create_default_wifi_sta();
|
|
275
|
+
}
|
|
276
|
+
if (!s_sta_netif) return ESP_FAIL;
|
|
277
|
+
|
|
278
|
+
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
279
|
+
err = esp_wifi_init(&cfg);
|
|
280
|
+
if (err != ESP_OK) {
|
|
281
|
+
/* Undo the netif creation so the next retry starts from a clean
|
|
282
|
+
* state (or a subsequent destroy can clean up predictably). */
|
|
283
|
+
esp_netif_destroy_default_wifi(s_sta_netif);
|
|
284
|
+
s_sta_netif = nullptr;
|
|
285
|
+
return err;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
|
289
|
+
mik__wifi_event_handler, nullptr, nullptr);
|
|
290
|
+
if (err != ESP_OK) goto fail_after_init;
|
|
291
|
+
|
|
292
|
+
err = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
|
293
|
+
mik__wifi_event_handler, nullptr, nullptr);
|
|
294
|
+
if (err != ESP_OK) goto fail_after_init;
|
|
295
|
+
|
|
296
|
+
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
|
297
|
+
if (err != ESP_OK) goto fail_after_init;
|
|
298
|
+
|
|
299
|
+
s_wifi_initialized = true;
|
|
300
|
+
return ESP_OK;
|
|
301
|
+
|
|
302
|
+
fail_after_init:
|
|
303
|
+
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, mik__wifi_event_handler);
|
|
304
|
+
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, mik__wifi_event_handler);
|
|
305
|
+
esp_wifi_deinit();
|
|
306
|
+
esp_netif_destroy_default_wifi(s_sta_netif);
|
|
307
|
+
s_sta_netif = nullptr;
|
|
308
|
+
return err;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Start the WiFi radio. Deferred from init so the radio is not active
|
|
312
|
+
* until connect() or scan() is actually called. */
|
|
313
|
+
static esp_err_t mik__wifi_ensure_started() {
|
|
314
|
+
esp_err_t err = mik__wifi_ensure_initialized();
|
|
315
|
+
if (err != ESP_OK) return err;
|
|
316
|
+
if (s_wifi_started) return ESP_OK;
|
|
317
|
+
|
|
318
|
+
err = esp_wifi_start();
|
|
319
|
+
if (err != ESP_OK) return err;
|
|
320
|
+
|
|
321
|
+
s_wifi_started = true;
|
|
322
|
+
return ESP_OK;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* ── Auth mode mapping ─────────────────────────────────────────────── */
|
|
326
|
+
|
|
327
|
+
static const char* mik__authmode_str(wifi_auth_mode_t mode) {
|
|
328
|
+
switch (mode) {
|
|
329
|
+
case WIFI_AUTH_OPEN:
|
|
330
|
+
return "open";
|
|
331
|
+
case WIFI_AUTH_WPA2_PSK:
|
|
332
|
+
return "wpa2-psk";
|
|
333
|
+
case WIFI_AUTH_WPA3_PSK:
|
|
334
|
+
return "wpa3-psk";
|
|
335
|
+
case WIFI_AUTH_WPA2_WPA3_PSK:
|
|
336
|
+
return "wpa2-wpa3-psk";
|
|
337
|
+
default:
|
|
338
|
+
return "unknown";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static wifi_auth_mode_t mik__authmode_from_str(const char* str) {
|
|
343
|
+
if (!str || strcmp(str, "open") == 0) return WIFI_AUTH_OPEN;
|
|
344
|
+
if (strcmp(str, "wpa2-psk") == 0) return WIFI_AUTH_WPA2_PSK;
|
|
345
|
+
if (strcmp(str, "wpa3-psk") == 0) return WIFI_AUTH_WPA3_PSK;
|
|
346
|
+
if (strcmp(str, "wpa2-wpa3-psk") == 0) return WIFI_AUTH_WPA2_WPA3_PSK;
|
|
347
|
+
return WIFI_AUTH_WPA2_PSK; // safe default
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Try to auto-configure the WiFi country from mikro.config.json.
|
|
351
|
+
* Returns true if country is configured (either already set or
|
|
352
|
+
* successfully auto-configured from config). */
|
|
353
|
+
static bool mik__wifi_try_auto_country(JSContext* ctx) {
|
|
354
|
+
if (s_country_configured) return true;
|
|
355
|
+
|
|
356
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
357
|
+
if (!mik_rt || mik_rt->config.wifi_country[0] == '\0') return false;
|
|
358
|
+
|
|
359
|
+
if (esp_wifi_set_country_code(mik_rt->config.wifi_country, true) != ESP_OK) return false;
|
|
360
|
+
s_country_configured = true;
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* ── JS class ──────────────────────────────────────────────────────── */
|
|
365
|
+
|
|
366
|
+
static JSClassID mik_wifi_class_id;
|
|
367
|
+
|
|
368
|
+
static JSClassDef mik_wifi_classdef = {
|
|
369
|
+
.class_name = "Wifi",
|
|
370
|
+
.finalizer = nullptr,
|
|
371
|
+
.gc_mark = nullptr,
|
|
372
|
+
.call = nullptr,
|
|
373
|
+
.exotic = nullptr,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
static JSValue mik__wifi_constructor(JSContext* ctx, JSValue new_target, int argc, JSValue* argv) {
|
|
377
|
+
esp_err_t err = mik__wifi_ensure_initialized();
|
|
378
|
+
if (err != ESP_OK) {
|
|
379
|
+
return JS_ThrowInternalError(ctx, "WiFi init failed: %s", esp_err_to_name(err));
|
|
380
|
+
}
|
|
381
|
+
return JS_NewObjectClass(ctx, mik_wifi_class_id);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static JSValue mik__wifi_connect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
385
|
+
if (!mik__wifi_try_auto_country(ctx)) {
|
|
386
|
+
return mik__result_err_tag(ctx, "CountryNotSet");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
esp_err_t start_err = mik__wifi_ensure_started();
|
|
390
|
+
if (start_err != ESP_OK) {
|
|
391
|
+
return mik__result_err_named(ctx, "StartFailed",
|
|
392
|
+
"Failed to start WiFi radio: %s", esp_err_to_name(start_err));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
396
|
+
CHECK_NOT_NULL(mik_rt);
|
|
397
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
398
|
+
|
|
399
|
+
if (mik__wifi_st(mik_rt)->connect_pending) {
|
|
400
|
+
return mik__result_err_tag(ctx, "ConnectInProgress");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const char* ssid = JS_ToCString(ctx, argv[0]);
|
|
404
|
+
if (!ssid) return JS_EXCEPTION;
|
|
405
|
+
const char* pass = JS_ToCString(ctx, argv[1]);
|
|
406
|
+
if (!pass) {
|
|
407
|
+
JS_FreeCString(ctx, ssid);
|
|
408
|
+
return JS_EXCEPTION;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
wifi_config_t wifi_config = {};
|
|
412
|
+
strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid), ssid, sizeof(wifi_config.sta.ssid) - 1);
|
|
413
|
+
strncpy(reinterpret_cast<char*>(wifi_config.sta.password), pass,
|
|
414
|
+
sizeof(wifi_config.sta.password) - 1);
|
|
415
|
+
|
|
416
|
+
JS_FreeCString(ctx, ssid);
|
|
417
|
+
JS_FreeCString(ctx, pass);
|
|
418
|
+
|
|
419
|
+
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
|
420
|
+
if (err != ESP_OK) {
|
|
421
|
+
return mik__result_err_named(ctx, "ConfigFailed",
|
|
422
|
+
"Failed to set WiFi STA config: %s", esp_err_to_name(err));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
err = esp_wifi_connect();
|
|
426
|
+
if (err != ESP_OK) {
|
|
427
|
+
return mik__result_err_named(ctx, "ConnectFailed",
|
|
428
|
+
"Failed to initiate WiFi connection: %s", esp_err_to_name(err));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Create promise that will be resolved when GOT_IP or resolved with error on failure
|
|
432
|
+
JSValue promise = MIK_InitPromise(ctx, &mik__wifi_st(mik_rt)->connect_promise);
|
|
433
|
+
mik__wifi_st(mik_rt)->connect_pending = true;
|
|
434
|
+
return mik__result_ok(ctx, promise);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static JSValue mik__wifi_disconnect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
438
|
+
esp_err_t err = esp_wifi_disconnect();
|
|
439
|
+
if (err != ESP_OK) {
|
|
440
|
+
return mik__result_err_named(ctx, "DisconnectFailed",
|
|
441
|
+
"Failed to disconnect from WiFi: %s", esp_err_to_name(err));
|
|
442
|
+
}
|
|
443
|
+
return mik__result_ok_void(ctx);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
static JSValue mik__wifi_rssi(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
447
|
+
wifi_ap_record_t ap_info;
|
|
448
|
+
esp_err_t err = esp_wifi_sta_get_ap_info(&ap_info);
|
|
449
|
+
if (err != ESP_OK) {
|
|
450
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
451
|
+
"Failed to get AP info for RSSI: %s", esp_err_to_name(err));
|
|
452
|
+
}
|
|
453
|
+
return mik__result_ok(ctx, JS_NewInt32(ctx, ap_info.rssi));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static JSValue mik__wifi_ip(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
457
|
+
if (!s_sta_netif) {
|
|
458
|
+
return JS_NewString(ctx, "0.0.0.0");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
esp_netif_ip_info_t ip_info;
|
|
462
|
+
esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip_info);
|
|
463
|
+
if (err != ESP_OK || ip_info.ip.addr == 0) {
|
|
464
|
+
return JS_NewString(ctx, "0.0.0.0");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
char ip_str[16];
|
|
468
|
+
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
|
|
469
|
+
return JS_NewString(ctx, ip_str);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
static JSValue mik__wifi_status(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
473
|
+
return JS_NewInt32(ctx, s_wifi_status);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
static JSValue mik__wifi_scan(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
477
|
+
if (!mik__wifi_try_auto_country(ctx)) {
|
|
478
|
+
return mik__result_err_tag(ctx, "CountryNotSet");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
esp_err_t start_err = mik__wifi_ensure_started();
|
|
482
|
+
if (start_err != ESP_OK) {
|
|
483
|
+
return mik__result_err_named(ctx, "StartFailed",
|
|
484
|
+
"Failed to start WiFi radio: %s", esp_err_to_name(start_err));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
488
|
+
CHECK_NOT_NULL(mik_rt);
|
|
489
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
490
|
+
|
|
491
|
+
if (mik__wifi_st(mik_rt)->scan_pending) {
|
|
492
|
+
return mik__result_err_tag(ctx, "ScanInProgress");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
wifi_scan_config_t scan_config = {};
|
|
496
|
+
scan_config.show_hidden = true;
|
|
497
|
+
|
|
498
|
+
// Parse optional scan filter options
|
|
499
|
+
uint8_t ssid_buf[33] = {};
|
|
500
|
+
if (argc > 0 && JS_IsObject(argv[0])) {
|
|
501
|
+
JSValue opts = argv[0];
|
|
502
|
+
|
|
503
|
+
JSValue ssid_val = JS_GetPropertyStr(ctx, opts, "ssid");
|
|
504
|
+
if (JS_IsString(ssid_val)) {
|
|
505
|
+
const char* ssid_str = JS_ToCString(ctx, ssid_val);
|
|
506
|
+
if (ssid_str) {
|
|
507
|
+
strncpy(reinterpret_cast<char*>(ssid_buf), ssid_str, sizeof(ssid_buf) - 1);
|
|
508
|
+
scan_config.ssid = ssid_buf;
|
|
509
|
+
JS_FreeCString(ctx, ssid_str);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
JS_FreeValue(ctx, ssid_val);
|
|
513
|
+
|
|
514
|
+
JSValue ch_val = JS_GetPropertyStr(ctx, opts, "channel");
|
|
515
|
+
if (JS_IsNumber(ch_val)) {
|
|
516
|
+
int32_t ch;
|
|
517
|
+
JS_ToInt32(ctx, &ch, ch_val);
|
|
518
|
+
scan_config.channel = ch;
|
|
519
|
+
}
|
|
520
|
+
JS_FreeValue(ctx, ch_val);
|
|
521
|
+
|
|
522
|
+
JSValue passive_val = JS_GetPropertyStr(ctx, opts, "passive");
|
|
523
|
+
if (JS_ToBool(ctx, passive_val)) {
|
|
524
|
+
scan_config.scan_type = WIFI_SCAN_TYPE_PASSIVE;
|
|
525
|
+
}
|
|
526
|
+
JS_FreeValue(ctx, passive_val);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
esp_err_t err = esp_wifi_scan_start(&scan_config, false); // non-blocking
|
|
530
|
+
if (err != ESP_OK) {
|
|
531
|
+
return mik__result_err_named(ctx, "ScanFailed",
|
|
532
|
+
"Failed to start WiFi scan: %s", esp_err_to_name(err));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
JSValue promise = MIK_InitPromise(ctx, &mik__wifi_st(mik_rt)->scan_promise);
|
|
536
|
+
mik__wifi_st(mik_rt)->scan_pending = true;
|
|
537
|
+
return mik__result_ok(ctx, promise);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
static JSValue mik__wifi_on(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
541
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
542
|
+
CHECK_NOT_NULL(mik_rt);
|
|
543
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
544
|
+
|
|
545
|
+
const char* event_name = JS_ToCString(ctx, argv[0]);
|
|
546
|
+
if (!event_name) return JS_EXCEPTION;
|
|
547
|
+
|
|
548
|
+
JSValue func = argv[1];
|
|
549
|
+
if (!JS_IsFunction(ctx, func)) {
|
|
550
|
+
JS_FreeCString(ctx, event_name);
|
|
551
|
+
return JS_ThrowTypeError(ctx, "expected argument 2 to be a function");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (strcmp(event_name, "connect") == 0) {
|
|
555
|
+
mik__wifi_st(mik_rt)->on_connect.push_back(JS_DupValue(ctx, func));
|
|
556
|
+
} else if (strcmp(event_name, "disconnect") == 0) {
|
|
557
|
+
mik__wifi_st(mik_rt)->on_disconnect.push_back(JS_DupValue(ctx, func));
|
|
558
|
+
} else if (strcmp(event_name, "station-connect") == 0) {
|
|
559
|
+
mik__wifi_st(mik_rt)->on_ap_sta_connect.push_back(JS_DupValue(ctx, func));
|
|
560
|
+
} else if (strcmp(event_name, "station-disconnect") == 0) {
|
|
561
|
+
mik__wifi_st(mik_rt)->on_ap_sta_disconnect.push_back(JS_DupValue(ctx, func));
|
|
562
|
+
} else if (strcmp(event_name, "rssi-low") == 0) {
|
|
563
|
+
mik__wifi_st(mik_rt)->on_rssi_low.push_back(JS_DupValue(ctx, func));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
JS_FreeCString(ctx, event_name);
|
|
567
|
+
return JS_UNDEFINED;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
static JSValue mik__wifi_off(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
571
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
572
|
+
CHECK_NOT_NULL(mik_rt);
|
|
573
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
574
|
+
|
|
575
|
+
const char* event_name = JS_ToCString(ctx, argv[0]);
|
|
576
|
+
if (!event_name) return JS_EXCEPTION;
|
|
577
|
+
|
|
578
|
+
JSValue func = argv[1];
|
|
579
|
+
std::vector<JSValue>* listeners = nullptr;
|
|
580
|
+
|
|
581
|
+
if (strcmp(event_name, "connect") == 0) {
|
|
582
|
+
listeners = &mik__wifi_st(mik_rt)->on_connect;
|
|
583
|
+
} else if (strcmp(event_name, "disconnect") == 0) {
|
|
584
|
+
listeners = &mik__wifi_st(mik_rt)->on_disconnect;
|
|
585
|
+
} else if (strcmp(event_name, "station-connect") == 0) {
|
|
586
|
+
listeners = &mik__wifi_st(mik_rt)->on_ap_sta_connect;
|
|
587
|
+
} else if (strcmp(event_name, "station-disconnect") == 0) {
|
|
588
|
+
listeners = &mik__wifi_st(mik_rt)->on_ap_sta_disconnect;
|
|
589
|
+
} else if (strcmp(event_name, "rssi-low") == 0) {
|
|
590
|
+
listeners = &mik__wifi_st(mik_rt)->on_rssi_low;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
JS_FreeCString(ctx, event_name);
|
|
594
|
+
|
|
595
|
+
if (listeners) {
|
|
596
|
+
for (auto it = listeners->begin(); it != listeners->end(); ++it) {
|
|
597
|
+
if (JS_IsSameValue(ctx, *it, func)) {
|
|
598
|
+
JS_FreeValue(ctx, *it);
|
|
599
|
+
listeners->erase(it);
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return JS_UNDEFINED;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* ── Phase 4: Network configuration ───────────────────────────────── */
|
|
609
|
+
|
|
610
|
+
static JSValue mik__wifi_mac(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
611
|
+
uint8_t mac[6];
|
|
612
|
+
esp_err_t err = esp_wifi_get_mac(WIFI_IF_STA, mac);
|
|
613
|
+
if (err != ESP_OK) {
|
|
614
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
615
|
+
"Failed to get WiFi MAC address: %s", esp_err_to_name(err));
|
|
616
|
+
}
|
|
617
|
+
char mac_str[18];
|
|
618
|
+
mik__format_mac(mac_str, sizeof(mac_str), mac);
|
|
619
|
+
return mik__result_ok(ctx, JS_NewString(ctx, mac_str));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static JSValue mik__wifi_get_hostname(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
623
|
+
if (!s_sta_netif) {
|
|
624
|
+
return JS_UNDEFINED;
|
|
625
|
+
}
|
|
626
|
+
const char* hostname = nullptr;
|
|
627
|
+
esp_err_t err = esp_netif_get_hostname(s_sta_netif, &hostname);
|
|
628
|
+
if (err != ESP_OK || !hostname) {
|
|
629
|
+
return JS_UNDEFINED;
|
|
630
|
+
}
|
|
631
|
+
return JS_NewString(ctx, hostname);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
static JSValue mik__wifi_set_hostname(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
635
|
+
if (!s_sta_netif) {
|
|
636
|
+
return mik__result_err_tag(ctx, "NotInitialized");
|
|
637
|
+
}
|
|
638
|
+
const char* hostname = JS_ToCString(ctx, argv[0]);
|
|
639
|
+
if (!hostname) return JS_EXCEPTION;
|
|
640
|
+
|
|
641
|
+
esp_err_t err = esp_netif_set_hostname(s_sta_netif, hostname);
|
|
642
|
+
JS_FreeCString(ctx, hostname);
|
|
643
|
+
if (err != ESP_OK) {
|
|
644
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
645
|
+
"Failed to set hostname: %s", esp_err_to_name(err));
|
|
646
|
+
}
|
|
647
|
+
return mik__result_ok_void(ctx);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
static JSValue mik__wifi_get_ip_config(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
651
|
+
if (!s_sta_netif) {
|
|
652
|
+
return JS_UNDEFINED;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
esp_netif_ip_info_t ip_info;
|
|
656
|
+
esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip_info);
|
|
657
|
+
if (err != ESP_OK) {
|
|
658
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
659
|
+
"Failed to get IP configuration: %s", esp_err_to_name(err));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
esp_netif_dns_info_t dns_info;
|
|
663
|
+
esp_netif_get_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns_info);
|
|
664
|
+
|
|
665
|
+
char ip_str[16], netmask_str[16], gw_str[16], dns_str[16];
|
|
666
|
+
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
|
|
667
|
+
snprintf(netmask_str, sizeof(netmask_str), IPSTR, IP2STR(&ip_info.netmask));
|
|
668
|
+
snprintf(gw_str, sizeof(gw_str), IPSTR, IP2STR(&ip_info.gw));
|
|
669
|
+
snprintf(dns_str, sizeof(dns_str), IPSTR, IP2STR(&dns_info.ip.u_addr.ip4));
|
|
670
|
+
|
|
671
|
+
JSValue obj = JS_NewObject(ctx);
|
|
672
|
+
JS_SetPropertyStr(ctx, obj, "ip", JS_NewString(ctx, ip_str));
|
|
673
|
+
JS_SetPropertyStr(ctx, obj, "netmask", JS_NewString(ctx, netmask_str));
|
|
674
|
+
JS_SetPropertyStr(ctx, obj, "gateway", JS_NewString(ctx, gw_str));
|
|
675
|
+
JS_SetPropertyStr(ctx, obj, "dns", JS_NewString(ctx, dns_str));
|
|
676
|
+
return mik__result_ok(ctx, obj);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
static JSValue mik__wifi_set_ip_config(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
680
|
+
if (!s_sta_netif) {
|
|
681
|
+
return mik__result_err_tag(ctx, "NotInitialized");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
JSValue opts = argv[0];
|
|
685
|
+
|
|
686
|
+
// Check for dhcp: true
|
|
687
|
+
JSValue dhcp_val = JS_GetPropertyStr(ctx, opts, "dhcp");
|
|
688
|
+
if (JS_ToBool(ctx, dhcp_val)) {
|
|
689
|
+
JS_FreeValue(ctx, dhcp_val);
|
|
690
|
+
esp_netif_dhcpc_start(s_sta_netif);
|
|
691
|
+
return mik__result_ok_void(ctx);
|
|
692
|
+
}
|
|
693
|
+
JS_FreeValue(ctx, dhcp_val);
|
|
694
|
+
|
|
695
|
+
// Stop DHCP before setting static IP
|
|
696
|
+
esp_netif_dhcpc_stop(s_sta_netif);
|
|
697
|
+
|
|
698
|
+
esp_netif_ip_info_t ip_info = {};
|
|
699
|
+
|
|
700
|
+
JSValue ip_val = JS_GetPropertyStr(ctx, opts, "ip");
|
|
701
|
+
JSValue netmask_val = JS_GetPropertyStr(ctx, opts, "netmask");
|
|
702
|
+
JSValue gw_val = JS_GetPropertyStr(ctx, opts, "gateway");
|
|
703
|
+
|
|
704
|
+
const char* ip_str = JS_ToCString(ctx, ip_val);
|
|
705
|
+
const char* netmask_str = JS_ToCString(ctx, netmask_val);
|
|
706
|
+
const char* gw_str = JS_ToCString(ctx, gw_val);
|
|
707
|
+
|
|
708
|
+
if (ip_str) esp_netif_str_to_ip4(ip_str, &ip_info.ip);
|
|
709
|
+
if (netmask_str) esp_netif_str_to_ip4(netmask_str, &ip_info.netmask);
|
|
710
|
+
if (gw_str) esp_netif_str_to_ip4(gw_str, &ip_info.gw);
|
|
711
|
+
|
|
712
|
+
if (ip_str) JS_FreeCString(ctx, ip_str);
|
|
713
|
+
if (netmask_str) JS_FreeCString(ctx, netmask_str);
|
|
714
|
+
if (gw_str) JS_FreeCString(ctx, gw_str);
|
|
715
|
+
JS_FreeValue(ctx, ip_val);
|
|
716
|
+
JS_FreeValue(ctx, netmask_val);
|
|
717
|
+
JS_FreeValue(ctx, gw_val);
|
|
718
|
+
|
|
719
|
+
esp_err_t err = esp_netif_set_ip_info(s_sta_netif, &ip_info);
|
|
720
|
+
if (err != ESP_OK) {
|
|
721
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
722
|
+
"Failed to set static IP configuration: %s", esp_err_to_name(err));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Set DNS if provided
|
|
726
|
+
JSValue dns_val = JS_GetPropertyStr(ctx, opts, "dns");
|
|
727
|
+
if (JS_IsString(dns_val)) {
|
|
728
|
+
const char* dns_str = JS_ToCString(ctx, dns_val);
|
|
729
|
+
if (dns_str) {
|
|
730
|
+
esp_netif_dns_info_t dns_info = {};
|
|
731
|
+
esp_netif_str_to_ip4(dns_str, &dns_info.ip.u_addr.ip4);
|
|
732
|
+
dns_info.ip.type = ESP_IPADDR_TYPE_V4;
|
|
733
|
+
esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns_info);
|
|
734
|
+
JS_FreeCString(ctx, dns_str);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
JS_FreeValue(ctx, dns_val);
|
|
738
|
+
|
|
739
|
+
return mik__result_ok_void(ctx);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/* ── Phase 5: Access Point mode ────────────────────────────────────── */
|
|
743
|
+
|
|
744
|
+
static JSValue mik__wifi_ap_start(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
745
|
+
if (!mik__wifi_try_auto_country(ctx)) {
|
|
746
|
+
return mik__result_err_tag(ctx, "CountryNotSet");
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
esp_err_t err = mik__wifi_ensure_started();
|
|
750
|
+
if (err != ESP_OK) {
|
|
751
|
+
return mik__result_err_named(ctx, "StartFailed",
|
|
752
|
+
"Failed to start WiFi radio: %s", esp_err_to_name(err));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Create AP netif if not already
|
|
756
|
+
if (!s_ap_netif) {
|
|
757
|
+
s_ap_netif = esp_netif_create_default_wifi_ap();
|
|
758
|
+
if (!s_ap_netif) {
|
|
759
|
+
return mik__result_err_named(ctx, "ApStartFailed",
|
|
760
|
+
"Failed to create AP network interface");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Switch to APSTA mode
|
|
765
|
+
err = esp_wifi_set_mode(WIFI_MODE_APSTA);
|
|
766
|
+
if (err != ESP_OK) {
|
|
767
|
+
return mik__result_err_named(ctx, "ApStartFailed",
|
|
768
|
+
"Failed to set APSTA mode: %s", esp_err_to_name(err));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
JSValue opts = argv[0];
|
|
772
|
+
wifi_config_t ap_config = {};
|
|
773
|
+
|
|
774
|
+
// SSID (required)
|
|
775
|
+
JSValue ssid_val = JS_GetPropertyStr(ctx, opts, "ssid");
|
|
776
|
+
const char* ssid = JS_ToCString(ctx, ssid_val);
|
|
777
|
+
if (!ssid) {
|
|
778
|
+
JS_FreeValue(ctx, ssid_val);
|
|
779
|
+
return JS_EXCEPTION;
|
|
780
|
+
}
|
|
781
|
+
strncpy(reinterpret_cast<char*>(ap_config.ap.ssid), ssid, sizeof(ap_config.ap.ssid) - 1);
|
|
782
|
+
ap_config.ap.ssid_len = strlen(ssid);
|
|
783
|
+
JS_FreeCString(ctx, ssid);
|
|
784
|
+
JS_FreeValue(ctx, ssid_val);
|
|
785
|
+
|
|
786
|
+
// Passphrase (optional — omit for open network)
|
|
787
|
+
JSValue pass_val = JS_GetPropertyStr(ctx, opts, "passphrase");
|
|
788
|
+
if (JS_IsString(pass_val)) {
|
|
789
|
+
const char* pass = JS_ToCString(ctx, pass_val);
|
|
790
|
+
if (pass && strlen(pass) > 0) {
|
|
791
|
+
strncpy(reinterpret_cast<char*>(ap_config.ap.password), pass,
|
|
792
|
+
sizeof(ap_config.ap.password) - 1);
|
|
793
|
+
ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
|
|
794
|
+
}
|
|
795
|
+
if (pass) JS_FreeCString(ctx, pass);
|
|
796
|
+
}
|
|
797
|
+
JS_FreeValue(ctx, pass_val);
|
|
798
|
+
|
|
799
|
+
// authMode override (optional)
|
|
800
|
+
JSValue auth_val = JS_GetPropertyStr(ctx, opts, "authMode");
|
|
801
|
+
if (JS_IsString(auth_val)) {
|
|
802
|
+
const char* auth_str = JS_ToCString(ctx, auth_val);
|
|
803
|
+
if (auth_str) {
|
|
804
|
+
ap_config.ap.authmode = mik__authmode_from_str(auth_str);
|
|
805
|
+
JS_FreeCString(ctx, auth_str);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
JS_FreeValue(ctx, auth_val);
|
|
809
|
+
|
|
810
|
+
// Channel (optional, default 1)
|
|
811
|
+
JSValue ch_val = JS_GetPropertyStr(ctx, opts, "channel");
|
|
812
|
+
if (JS_IsNumber(ch_val)) {
|
|
813
|
+
int32_t ch;
|
|
814
|
+
JS_ToInt32(ctx, &ch, ch_val);
|
|
815
|
+
ap_config.ap.channel = ch;
|
|
816
|
+
} else {
|
|
817
|
+
ap_config.ap.channel = 1;
|
|
818
|
+
}
|
|
819
|
+
JS_FreeValue(ctx, ch_val);
|
|
820
|
+
|
|
821
|
+
// Hidden (optional)
|
|
822
|
+
JSValue hidden_val = JS_GetPropertyStr(ctx, opts, "hidden");
|
|
823
|
+
ap_config.ap.ssid_hidden = JS_ToBool(ctx, hidden_val);
|
|
824
|
+
JS_FreeValue(ctx, hidden_val);
|
|
825
|
+
|
|
826
|
+
// Max connections (optional, default 4)
|
|
827
|
+
JSValue max_val = JS_GetPropertyStr(ctx, opts, "maxConnections");
|
|
828
|
+
if (JS_IsNumber(max_val)) {
|
|
829
|
+
int32_t max_conn;
|
|
830
|
+
JS_ToInt32(ctx, &max_conn, max_val);
|
|
831
|
+
ap_config.ap.max_connection = max_conn;
|
|
832
|
+
} else {
|
|
833
|
+
ap_config.ap.max_connection = MIK_WIFI_MAX_AP_STATIONS;
|
|
834
|
+
}
|
|
835
|
+
JS_FreeValue(ctx, max_val);
|
|
836
|
+
|
|
837
|
+
err = esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
|
838
|
+
if (err != ESP_OK) {
|
|
839
|
+
return mik__result_err_named(ctx, "ConfigFailed",
|
|
840
|
+
"Failed to set WiFi AP config: %s", esp_err_to_name(err));
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return mik__result_ok_void(ctx);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
static JSValue mik__wifi_ap_stop(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
847
|
+
// Switch back to STA-only mode
|
|
848
|
+
esp_err_t err = esp_wifi_set_mode(WIFI_MODE_STA);
|
|
849
|
+
if (err != ESP_OK) {
|
|
850
|
+
return mik__result_err_named(ctx, "ApStopFailed",
|
|
851
|
+
"Failed to stop AP mode: %s", esp_err_to_name(err));
|
|
852
|
+
}
|
|
853
|
+
return mik__result_ok_void(ctx);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
static JSValue mik__wifi_ap_is_active(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
857
|
+
wifi_mode_t mode;
|
|
858
|
+
esp_err_t err = esp_wifi_get_mode(&mode);
|
|
859
|
+
if (err != ESP_OK) {
|
|
860
|
+
return JS_FALSE;
|
|
861
|
+
}
|
|
862
|
+
return JS_NewBool(ctx, mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
static JSValue mik__wifi_ap_ip(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
866
|
+
if (!s_ap_netif) {
|
|
867
|
+
return JS_UNDEFINED;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
esp_netif_ip_info_t ip_info;
|
|
871
|
+
esp_err_t err = esp_netif_get_ip_info(s_ap_netif, &ip_info);
|
|
872
|
+
if (err != ESP_OK || ip_info.ip.addr == 0) {
|
|
873
|
+
return JS_UNDEFINED;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
char ip_str[16];
|
|
877
|
+
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
|
|
878
|
+
return JS_NewString(ctx, ip_str);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
static JSValue mik__wifi_ap_stations(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
882
|
+
wifi_sta_list_t sta_list;
|
|
883
|
+
esp_err_t err = esp_wifi_ap_get_sta_list(&sta_list);
|
|
884
|
+
if (err != ESP_OK) {
|
|
885
|
+
return JS_NewArray(ctx);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
JSValue arr = JS_NewArray(ctx);
|
|
889
|
+
for (int i = 0; i < sta_list.num; i++) {
|
|
890
|
+
JSValue obj = JS_NewObject(ctx);
|
|
891
|
+
char mac_str[18];
|
|
892
|
+
mik__format_mac(mac_str, sizeof(mac_str), sta_list.sta[i].mac);
|
|
893
|
+
JS_SetPropertyStr(ctx, obj, "mac", JS_NewString(ctx, mac_str));
|
|
894
|
+
JS_SetPropertyStr(ctx, obj, "rssi", JS_NewInt32(ctx, sta_list.sta[i].rssi));
|
|
895
|
+
JS_SetPropertyUint32(ctx, arr, i, obj);
|
|
896
|
+
}
|
|
897
|
+
return arr;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/* ── Phase 6: Power management & misc ──────────────────────────────── */
|
|
901
|
+
|
|
902
|
+
static JSValue mik__wifi_get_power_save(JSContext* ctx, JSValue this_val, int argc,
|
|
903
|
+
JSValue* argv) {
|
|
904
|
+
wifi_ps_type_t ps;
|
|
905
|
+
esp_err_t err = esp_wifi_get_ps(&ps);
|
|
906
|
+
if (err != ESP_OK) {
|
|
907
|
+
return JS_NewString(ctx, "none");
|
|
908
|
+
}
|
|
909
|
+
switch (ps) {
|
|
910
|
+
case WIFI_PS_NONE:
|
|
911
|
+
return JS_NewString(ctx, "none");
|
|
912
|
+
case WIFI_PS_MIN_MODEM:
|
|
913
|
+
return JS_NewString(ctx, "min");
|
|
914
|
+
case WIFI_PS_MAX_MODEM:
|
|
915
|
+
return JS_NewString(ctx, "max");
|
|
916
|
+
default:
|
|
917
|
+
return JS_NewString(ctx, "none");
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
static JSValue mik__wifi_set_power_save(JSContext* ctx, JSValue this_val, int argc,
|
|
922
|
+
JSValue* argv) {
|
|
923
|
+
const char* mode = JS_ToCString(ctx, argv[0]);
|
|
924
|
+
if (!mode) return JS_EXCEPTION;
|
|
925
|
+
|
|
926
|
+
wifi_ps_type_t ps = WIFI_PS_NONE;
|
|
927
|
+
if (strcmp(mode, "min") == 0) {
|
|
928
|
+
ps = WIFI_PS_MIN_MODEM;
|
|
929
|
+
} else if (strcmp(mode, "max") == 0) {
|
|
930
|
+
ps = WIFI_PS_MAX_MODEM;
|
|
931
|
+
}
|
|
932
|
+
JS_FreeCString(ctx, mode);
|
|
933
|
+
|
|
934
|
+
esp_err_t err = esp_wifi_set_ps(ps);
|
|
935
|
+
if (err != ESP_OK) {
|
|
936
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
937
|
+
"Failed to set power save mode: %s", esp_err_to_name(err));
|
|
938
|
+
}
|
|
939
|
+
return mik__result_ok_void(ctx);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
static JSValue mik__wifi_get_country(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
943
|
+
/* esp_wifi_get_country_code writes "CCO\0" (country + operating class +
|
|
944
|
+
* null) — 4 bytes. A 3-byte buffer overflows and leaks adjacent stack
|
|
945
|
+
* into the JS string ("US B" instead of "US"). Return just the 2-char
|
|
946
|
+
* country code; callers that want the operating class can add an API. */
|
|
947
|
+
char cc[4] = {};
|
|
948
|
+
esp_err_t err = esp_wifi_get_country_code(cc);
|
|
949
|
+
if (err != ESP_OK) {
|
|
950
|
+
return JS_UNDEFINED;
|
|
951
|
+
}
|
|
952
|
+
return JS_NewStringLen(ctx, cc, 2);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
static JSValue mik__wifi_set_country(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
956
|
+
const char* cc = JS_ToCString(ctx, argv[0]);
|
|
957
|
+
if (!cc) return JS_EXCEPTION;
|
|
958
|
+
|
|
959
|
+
esp_err_t err = esp_wifi_set_country_code(cc, true);
|
|
960
|
+
JS_FreeCString(ctx, cc);
|
|
961
|
+
|
|
962
|
+
if (err != ESP_OK) {
|
|
963
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
964
|
+
"Failed to set WiFi country code: %s", esp_err_to_name(err));
|
|
965
|
+
}
|
|
966
|
+
s_country_configured = true;
|
|
967
|
+
return mik__result_ok_void(ctx);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* ── TX power ──────────────────────────────────────────────────────── */
|
|
971
|
+
|
|
972
|
+
static JSValue mik__wifi_get_tx_power(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
973
|
+
/* esp_wifi_get_max_tx_power requires the radio to be started. */
|
|
974
|
+
esp_err_t start_err = mik__wifi_ensure_started();
|
|
975
|
+
if (start_err != ESP_OK) {
|
|
976
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
977
|
+
"Failed to get TX power: %s", esp_err_to_name(start_err));
|
|
978
|
+
}
|
|
979
|
+
int8_t power;
|
|
980
|
+
esp_err_t err = esp_wifi_get_max_tx_power(&power);
|
|
981
|
+
if (err != ESP_OK) {
|
|
982
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
983
|
+
"Failed to get TX power: %s", esp_err_to_name(err));
|
|
984
|
+
}
|
|
985
|
+
return mik__result_ok(ctx, JS_NewFloat64(ctx, power * 0.25));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
static JSValue mik__wifi_set_tx_power(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
989
|
+
double dbm;
|
|
990
|
+
if (JS_ToFloat64(ctx, &dbm, argv[0])) return JS_EXCEPTION;
|
|
991
|
+
esp_err_t start_err = mik__wifi_ensure_started();
|
|
992
|
+
if (start_err != ESP_OK) {
|
|
993
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
994
|
+
"Failed to set TX power: %s", esp_err_to_name(start_err));
|
|
995
|
+
}
|
|
996
|
+
auto power = static_cast<int8_t>(dbm * 4.0);
|
|
997
|
+
esp_err_t err = esp_wifi_set_max_tx_power(power);
|
|
998
|
+
if (err != ESP_OK) {
|
|
999
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
1000
|
+
"Failed to set TX power: %s", esp_err_to_name(err));
|
|
1001
|
+
}
|
|
1002
|
+
return mik__result_ok_void(ctx);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/* ── RSSI threshold ───────────────────────────────────────────────── */
|
|
1006
|
+
|
|
1007
|
+
static JSValue mik__wifi_set_rssi_threshold(JSContext* ctx, JSValue this_val, int argc,
|
|
1008
|
+
JSValue* argv) {
|
|
1009
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1010
|
+
CHECK_NOT_NULL(mik_rt);
|
|
1011
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
1012
|
+
|
|
1013
|
+
int32_t threshold;
|
|
1014
|
+
if (JS_ToInt32(ctx, &threshold, argv[0])) return JS_EXCEPTION;
|
|
1015
|
+
|
|
1016
|
+
esp_err_t err = esp_wifi_set_rssi_threshold(threshold);
|
|
1017
|
+
if (err != ESP_OK) {
|
|
1018
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
1019
|
+
"Failed to set RSSI threshold: %s", esp_err_to_name(err));
|
|
1020
|
+
}
|
|
1021
|
+
mik__wifi_st(mik_rt)->rssi_threshold = threshold;
|
|
1022
|
+
return mik__result_ok_void(ctx);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
static JSValue mik__wifi_get_rssi_threshold(JSContext* ctx, JSValue this_val, int argc,
|
|
1026
|
+
JSValue* argv) {
|
|
1027
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1028
|
+
CHECK_NOT_NULL(mik_rt);
|
|
1029
|
+
CHECK_NOT_NULL(mik__wifi_st(mik_rt));
|
|
1030
|
+
return JS_NewInt32(ctx, mik__wifi_st(mik_rt)->rssi_threshold);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* ── AP deauth station ────────────────────────────────────────────── */
|
|
1034
|
+
|
|
1035
|
+
static JSValue mik__wifi_ap_deauth_station(JSContext* ctx, JSValue this_val, int argc,
|
|
1036
|
+
JSValue* argv) {
|
|
1037
|
+
const char* mac_str = JS_ToCString(ctx, argv[0]);
|
|
1038
|
+
if (!mac_str) return JS_EXCEPTION;
|
|
1039
|
+
|
|
1040
|
+
uint8_t mac[6];
|
|
1041
|
+
if (!mik__parse_mac(mac_str, mac)) {
|
|
1042
|
+
JS_FreeCString(ctx, mac_str);
|
|
1043
|
+
return JS_ThrowTypeError(ctx, "Invalid MAC address format; expected XX:XX:XX:XX:XX:XX");
|
|
1044
|
+
}
|
|
1045
|
+
JS_FreeCString(ctx, mac_str);
|
|
1046
|
+
|
|
1047
|
+
uint16_t aid = 0;
|
|
1048
|
+
esp_err_t err = esp_wifi_ap_get_sta_aid(mac, &aid);
|
|
1049
|
+
if (err != ESP_OK || aid == 0) {
|
|
1050
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
1051
|
+
"Failed to find station AID for deauth: %s", esp_err_to_name(err));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
err = esp_wifi_deauth_sta(aid);
|
|
1055
|
+
if (err != ESP_OK) {
|
|
1056
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
1057
|
+
"Failed to deauthenticate station: %s", esp_err_to_name(err));
|
|
1058
|
+
}
|
|
1059
|
+
return mik__result_ok_void(ctx);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/* ── AP inactive timeout ──────────────────────────────────────────── */
|
|
1063
|
+
|
|
1064
|
+
static JSValue mik__wifi_ap_get_inactive_timeout(JSContext* ctx, JSValue this_val, int argc,
|
|
1065
|
+
JSValue* argv) {
|
|
1066
|
+
uint16_t sec;
|
|
1067
|
+
esp_err_t err = esp_wifi_get_inactive_time(WIFI_IF_AP, &sec);
|
|
1068
|
+
if (err != ESP_OK) {
|
|
1069
|
+
return mik__result_err_named(ctx, "GetFailed",
|
|
1070
|
+
"Failed to get AP inactive timeout: %s", esp_err_to_name(err));
|
|
1071
|
+
}
|
|
1072
|
+
return mik__result_ok(ctx, JS_NewInt32(ctx, sec));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
static JSValue mik__wifi_ap_set_inactive_timeout(JSContext* ctx, JSValue this_val, int argc,
|
|
1076
|
+
JSValue* argv) {
|
|
1077
|
+
int32_t sec;
|
|
1078
|
+
if (JS_ToInt32(ctx, &sec, argv[0])) return JS_EXCEPTION;
|
|
1079
|
+
esp_err_t err = esp_wifi_set_inactive_time(WIFI_IF_AP, static_cast<uint16_t>(sec));
|
|
1080
|
+
if (err != ESP_OK) {
|
|
1081
|
+
return mik__result_err_named(ctx, "SetFailed",
|
|
1082
|
+
"Failed to set AP inactive timeout: %s", esp_err_to_name(err));
|
|
1083
|
+
}
|
|
1084
|
+
return mik__result_ok_void(ctx);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/* ── Function table ────────────────────────────────────────────────── */
|
|
1088
|
+
|
|
1089
|
+
static const JSCFunctionListEntry mik__wifi_proto_funcs[] = {
|
|
1090
|
+
MIK_CFUNC_DEF("connect", 2, mik__wifi_connect),
|
|
1091
|
+
MIK_CFUNC_DEF("disconnect", 0, mik__wifi_disconnect),
|
|
1092
|
+
MIK_CFUNC_DEF("rssi", 0, mik__wifi_rssi),
|
|
1093
|
+
MIK_CFUNC_DEF("ip", 0, mik__wifi_ip),
|
|
1094
|
+
MIK_CFUNC_DEF("status", 0, mik__wifi_status),
|
|
1095
|
+
MIK_CFUNC_DEF("scan", 1, mik__wifi_scan),
|
|
1096
|
+
MIK_CFUNC_DEF("on", 2, mik__wifi_on),
|
|
1097
|
+
MIK_CFUNC_DEF("off", 2, mik__wifi_off),
|
|
1098
|
+
MIK_CFUNC_DEF("mac", 0, mik__wifi_mac),
|
|
1099
|
+
MIK_CFUNC_DEF("getHostname", 0, mik__wifi_get_hostname),
|
|
1100
|
+
MIK_CFUNC_DEF("setHostname", 1, mik__wifi_set_hostname),
|
|
1101
|
+
MIK_CFUNC_DEF("getIpConfig", 0, mik__wifi_get_ip_config),
|
|
1102
|
+
MIK_CFUNC_DEF("setIpConfig", 1, mik__wifi_set_ip_config),
|
|
1103
|
+
MIK_CFUNC_DEF("apStart", 1, mik__wifi_ap_start),
|
|
1104
|
+
MIK_CFUNC_DEF("apStop", 0, mik__wifi_ap_stop),
|
|
1105
|
+
MIK_CFUNC_DEF("apIsActive", 0, mik__wifi_ap_is_active),
|
|
1106
|
+
MIK_CFUNC_DEF("apIp", 0, mik__wifi_ap_ip),
|
|
1107
|
+
MIK_CFUNC_DEF("apStations", 0, mik__wifi_ap_stations),
|
|
1108
|
+
MIK_CFUNC_DEF("getPowerSave", 0, mik__wifi_get_power_save),
|
|
1109
|
+
MIK_CFUNC_DEF("setPowerSave", 1, mik__wifi_set_power_save),
|
|
1110
|
+
MIK_CFUNC_DEF("getCountry", 0, mik__wifi_get_country),
|
|
1111
|
+
MIK_CFUNC_DEF("setCountry", 1, mik__wifi_set_country),
|
|
1112
|
+
MIK_CFUNC_DEF("getTxPower", 0, mik__wifi_get_tx_power),
|
|
1113
|
+
MIK_CFUNC_DEF("setTxPower", 1, mik__wifi_set_tx_power),
|
|
1114
|
+
MIK_CFUNC_DEF("getRssiThreshold", 0, mik__wifi_get_rssi_threshold),
|
|
1115
|
+
MIK_CFUNC_DEF("setRssiThreshold", 1, mik__wifi_set_rssi_threshold),
|
|
1116
|
+
MIK_CFUNC_DEF("apDeauthStation", 1, mik__wifi_ap_deauth_station),
|
|
1117
|
+
MIK_CFUNC_DEF("apGetInactiveTimeout", 0, mik__wifi_ap_get_inactive_timeout),
|
|
1118
|
+
MIK_CFUNC_DEF("apSetInactiveTimeout", 1, mik__wifi_ap_set_inactive_timeout),
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
/* ── Module init ───────────────────────────────────────────────────── */
|
|
1122
|
+
|
|
1123
|
+
static int mik__wifi_module_init(JSContext* ctx, JSModuleDef* m) {
|
|
1124
|
+
JSRuntime* rt = JS_GetRuntime(ctx);
|
|
1125
|
+
|
|
1126
|
+
JS_NewClassID(rt, &mik_wifi_class_id);
|
|
1127
|
+
JS_NewClass(rt, mik_wifi_class_id, &mik_wifi_classdef);
|
|
1128
|
+
|
|
1129
|
+
JSValue proto = JS_NewObject(ctx);
|
|
1130
|
+
JS_SetPropertyFunctionList(ctx, proto, mik__wifi_proto_funcs, countof(mik__wifi_proto_funcs));
|
|
1131
|
+
JS_SetClassProto(ctx, mik_wifi_class_id, proto);
|
|
1132
|
+
|
|
1133
|
+
JSValue ctor = JS_NewCFunction2(ctx, mik__wifi_constructor, "Wifi", 0,
|
|
1134
|
+
JS_CFUNC_constructor, 0);
|
|
1135
|
+
JS_SetModuleExport(ctx, m, "Wifi", ctor);
|
|
1136
|
+
return 0;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
static JSModuleDef* mik__wifi_init(JSContext* ctx) {
|
|
1140
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1141
|
+
CHECK_NOT_NULL(mik_rt);
|
|
1142
|
+
mik__wifi_slot = MIK_AllocModuleSlot(mik_rt);
|
|
1143
|
+
|
|
1144
|
+
auto* state = new MIKWifiState();
|
|
1145
|
+
state->event_queue = xQueueCreate(8, sizeof(MIKWifiEvent));
|
|
1146
|
+
CHECK_NOT_NULL(state->event_queue);
|
|
1147
|
+
state->connect_pending = false;
|
|
1148
|
+
state->scan_pending = false;
|
|
1149
|
+
state->on_connect.reserve(4);
|
|
1150
|
+
state->on_disconnect.reserve(4);
|
|
1151
|
+
state->on_ap_sta_connect.reserve(4);
|
|
1152
|
+
state->on_ap_sta_disconnect.reserve(4);
|
|
1153
|
+
state->on_rssi_low.reserve(4);
|
|
1154
|
+
s_event_queue = state->event_queue;
|
|
1155
|
+
mik__wifi_st(mik_rt) = state;
|
|
1156
|
+
|
|
1157
|
+
JSModuleDef* m = JS_NewCModule(ctx, "native:wifi", mik__wifi_module_init);
|
|
1158
|
+
if (!m) return nullptr;
|
|
1159
|
+
JS_AddModuleExport(ctx, m, "Wifi");
|
|
1160
|
+
return m;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/* ── Event loop consumption ────────────────────────────────────────── */
|
|
1164
|
+
|
|
1165
|
+
static const char* mik__disconnect_reason_str(MIKWifiStatus status) {
|
|
1166
|
+
switch (status) {
|
|
1167
|
+
case MIK_WIFI_NO_SSID_AVAIL:
|
|
1168
|
+
return "no-ssid";
|
|
1169
|
+
case MIK_WIFI_CONNECT_FAILED:
|
|
1170
|
+
return "auth-failed";
|
|
1171
|
+
case MIK_WIFI_CONNECTION_LOST:
|
|
1172
|
+
return "connection-lost";
|
|
1173
|
+
default:
|
|
1174
|
+
return "disconnected";
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
static void mik__wifi_dispatch_connect(JSContext* ctx, MIKWifiState* state,
|
|
1179
|
+
esp_netif_ip_info_t* ip_info) {
|
|
1180
|
+
char ip_str[16], netmask_str[16], gw_str[16];
|
|
1181
|
+
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info->ip));
|
|
1182
|
+
snprintf(netmask_str, sizeof(netmask_str), IPSTR, IP2STR(&ip_info->netmask));
|
|
1183
|
+
snprintf(gw_str, sizeof(gw_str), IPSTR, IP2STR(&ip_info->gw));
|
|
1184
|
+
|
|
1185
|
+
JSValue info = JS_NewObject(ctx);
|
|
1186
|
+
JS_SetPropertyStr(ctx, info, "ip", JS_NewString(ctx, ip_str));
|
|
1187
|
+
JS_SetPropertyStr(ctx, info, "netmask", JS_NewString(ctx, netmask_str));
|
|
1188
|
+
JS_SetPropertyStr(ctx, info, "gateway", JS_NewString(ctx, gw_str));
|
|
1189
|
+
|
|
1190
|
+
for (auto& listener : state->on_connect) {
|
|
1191
|
+
mik_call_handler(ctx, listener, 1, &info);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
JS_FreeValue(ctx, info);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
static void mik__wifi_dispatch_disconnect(JSContext* ctx, MIKWifiState* state,
|
|
1198
|
+
MIKWifiStatus status) {
|
|
1199
|
+
const char* reason = mik__disconnect_reason_str(status);
|
|
1200
|
+
JSValue reason_val = JS_NewString(ctx, reason);
|
|
1201
|
+
|
|
1202
|
+
for (auto& listener : state->on_disconnect) {
|
|
1203
|
+
mik_call_handler(ctx, listener, 1, &reason_val);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
JS_FreeValue(ctx, reason_val);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
static void mik__wifi_dispatch_ap_sta(JSContext* ctx, std::vector<JSValue>& listeners,
|
|
1210
|
+
const uint8_t* mac) {
|
|
1211
|
+
char mac_str[18];
|
|
1212
|
+
mik__format_mac(mac_str, sizeof(mac_str), mac);
|
|
1213
|
+
|
|
1214
|
+
JSValue info = JS_NewObject(ctx);
|
|
1215
|
+
JS_SetPropertyStr(ctx, info, "mac", JS_NewString(ctx, mac_str));
|
|
1216
|
+
|
|
1217
|
+
for (auto& listener : listeners) {
|
|
1218
|
+
mik_call_handler(ctx, listener, 1, &info);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
JS_FreeValue(ctx, info);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
static void mik__wifi_resolve_scan(JSContext* ctx, MIKWifiState* state) {
|
|
1225
|
+
uint16_t ap_count = 0;
|
|
1226
|
+
esp_wifi_scan_get_ap_num(&ap_count);
|
|
1227
|
+
|
|
1228
|
+
if (ap_count > MIK_WIFI_MAX_SCAN_RESULTS) {
|
|
1229
|
+
ap_count = MIK_WIFI_MAX_SCAN_RESULTS;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (ap_count == 0) {
|
|
1233
|
+
JSValue arr = JS_NewArray(ctx);
|
|
1234
|
+
JSValue result = mik__result_ok(ctx, arr);
|
|
1235
|
+
MIK_ResolvePromise(ctx, &state->scan_promise, 1, &result);
|
|
1236
|
+
state->scan_pending = false;
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
auto* ap_records = static_cast<wifi_ap_record_t*>(calloc(ap_count, sizeof(wifi_ap_record_t)));
|
|
1241
|
+
if (!ap_records) {
|
|
1242
|
+
esp_wifi_clear_ap_list();
|
|
1243
|
+
JSValue result = mik__result_err_named(ctx, "ScanFailed",
|
|
1244
|
+
"Failed to allocate memory for scan results");
|
|
1245
|
+
MIK_ResolvePromise(ctx, &state->scan_promise, 1, &result);
|
|
1246
|
+
state->scan_pending = false;
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
esp_wifi_scan_get_ap_records(&ap_count, ap_records);
|
|
1251
|
+
|
|
1252
|
+
JSValue arr = JS_NewArray(ctx);
|
|
1253
|
+
for (uint16_t i = 0; i < ap_count; i++) {
|
|
1254
|
+
JSValue obj = JS_NewObject(ctx);
|
|
1255
|
+
JS_SetPropertyStr(
|
|
1256
|
+
ctx, obj, "ssid",
|
|
1257
|
+
mik__new_string_from_ssid(ctx, ap_records[i].ssid, sizeof(ap_records[i].ssid)));
|
|
1258
|
+
|
|
1259
|
+
char bssid_str[18];
|
|
1260
|
+
mik__format_mac(bssid_str, sizeof(bssid_str), ap_records[i].bssid);
|
|
1261
|
+
JS_SetPropertyStr(ctx, obj, "bssid", JS_NewString(ctx, bssid_str));
|
|
1262
|
+
|
|
1263
|
+
JS_SetPropertyStr(ctx, obj, "channel", JS_NewInt32(ctx, ap_records[i].primary));
|
|
1264
|
+
JS_SetPropertyStr(ctx, obj, "rssi", JS_NewInt32(ctx, ap_records[i].rssi));
|
|
1265
|
+
JS_SetPropertyStr(ctx, obj, "authMode",
|
|
1266
|
+
JS_NewString(ctx, mik__authmode_str(ap_records[i].authmode)));
|
|
1267
|
+
JS_SetPropertyStr(ctx, obj, "hidden",
|
|
1268
|
+
JS_NewBool(ctx, ap_records[i].ssid[0] == '\0'));
|
|
1269
|
+
|
|
1270
|
+
JS_SetPropertyUint32(ctx, arr, i, obj);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
free(ap_records);
|
|
1274
|
+
|
|
1275
|
+
JSValue result = mik__result_ok(ctx, arr);
|
|
1276
|
+
MIK_ResolvePromise(ctx, &state->scan_promise, 1, &result);
|
|
1277
|
+
state->scan_pending = false;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
void mik__wifi_consume(JSContext* ctx) {
|
|
1281
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1282
|
+
CHECK_NOT_NULL(mik_rt);
|
|
1283
|
+
if (!mik__wifi_st(mik_rt)) return;
|
|
1284
|
+
|
|
1285
|
+
MIKWifiState* state = mik__wifi_st(mik_rt);
|
|
1286
|
+
MIKWifiEvent evt;
|
|
1287
|
+
|
|
1288
|
+
while (xQueueReceive(state->event_queue, &evt, 0) == pdTRUE) {
|
|
1289
|
+
switch (evt.type) {
|
|
1290
|
+
case MIK_WIFI_EVT_GOT_IP: {
|
|
1291
|
+
// Resolve connect promise
|
|
1292
|
+
if (state->connect_pending) {
|
|
1293
|
+
char ip_str[16], netmask_str[16], gw_str[16];
|
|
1294
|
+
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&evt.ip_info.ip));
|
|
1295
|
+
snprintf(netmask_str, sizeof(netmask_str), IPSTR,
|
|
1296
|
+
IP2STR(&evt.ip_info.netmask));
|
|
1297
|
+
snprintf(gw_str, sizeof(gw_str), IPSTR, IP2STR(&evt.ip_info.gw));
|
|
1298
|
+
|
|
1299
|
+
JSValue info = JS_NewObject(ctx);
|
|
1300
|
+
JS_SetPropertyStr(ctx, info, "ip", JS_NewString(ctx, ip_str));
|
|
1301
|
+
JS_SetPropertyStr(ctx, info, "netmask", JS_NewString(ctx, netmask_str));
|
|
1302
|
+
JS_SetPropertyStr(ctx, info, "gateway", JS_NewString(ctx, gw_str));
|
|
1303
|
+
|
|
1304
|
+
JSValue result = mik__result_ok(ctx, info);
|
|
1305
|
+
MIK_ResolvePromise(ctx, &state->connect_promise, 1, &result);
|
|
1306
|
+
state->connect_pending = false;
|
|
1307
|
+
}
|
|
1308
|
+
// Dispatch connect event listeners
|
|
1309
|
+
mik__wifi_dispatch_connect(ctx, state, &evt.ip_info);
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1312
|
+
case MIK_WIFI_EVT_STATUS_CHANGE: {
|
|
1313
|
+
// Check if this is a connect failure
|
|
1314
|
+
if (state->connect_pending) {
|
|
1315
|
+
if (evt.status == MIK_WIFI_NO_SSID_AVAIL ||
|
|
1316
|
+
evt.status == MIK_WIFI_CONNECT_FAILED ||
|
|
1317
|
+
evt.status == MIK_WIFI_DISCONNECTED) {
|
|
1318
|
+
JSValue result = mik__result_err_named(ctx, "ConnectFailed",
|
|
1319
|
+
"WiFi connection failed: %s",
|
|
1320
|
+
mik__disconnect_reason_str(evt.status));
|
|
1321
|
+
MIK_ResolvePromise(ctx, &state->connect_promise, 1, &result);
|
|
1322
|
+
state->connect_pending = false;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Dispatch disconnect event listeners
|
|
1327
|
+
if (evt.status == MIK_WIFI_NO_SSID_AVAIL ||
|
|
1328
|
+
evt.status == MIK_WIFI_CONNECT_FAILED ||
|
|
1329
|
+
evt.status == MIK_WIFI_CONNECTION_LOST ||
|
|
1330
|
+
evt.status == MIK_WIFI_DISCONNECTED) {
|
|
1331
|
+
mik__wifi_dispatch_disconnect(ctx, state, evt.status);
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
case MIK_WIFI_EVT_SCAN_DONE: {
|
|
1336
|
+
if (state->scan_pending) {
|
|
1337
|
+
mik__wifi_resolve_scan(ctx, state);
|
|
1338
|
+
}
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
case MIK_WIFI_EVT_AP_STA_CONNECTED: {
|
|
1342
|
+
mik__wifi_dispatch_ap_sta(ctx, state->on_ap_sta_connect, evt.mac);
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case MIK_WIFI_EVT_AP_STA_DISCONNECTED: {
|
|
1346
|
+
mik__wifi_dispatch_ap_sta(ctx, state->on_ap_sta_disconnect, evt.mac);
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case MIK_WIFI_EVT_RSSI_LOW: {
|
|
1350
|
+
JSValue rssi_val = JS_NewInt32(ctx, evt.rssi);
|
|
1351
|
+
for (auto& listener : state->on_rssi_low) {
|
|
1352
|
+
mik_call_handler(ctx, listener, 1, &rssi_val);
|
|
1353
|
+
}
|
|
1354
|
+
JS_FreeValue(ctx, rssi_val);
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
void mik__wifi_destroy(JSContext* ctx) {
|
|
1362
|
+
MIKRuntime* mik_rt = MIK_GetRuntime(ctx);
|
|
1363
|
+
CHECK_NOT_NULL(mik_rt);
|
|
1364
|
+
if (!mik__wifi_st(mik_rt)) return;
|
|
1365
|
+
|
|
1366
|
+
MIKWifiState* state = mik__wifi_st(mik_rt);
|
|
1367
|
+
|
|
1368
|
+
// Free pending promises
|
|
1369
|
+
if (state->connect_pending) {
|
|
1370
|
+
MIK_FreePromise(ctx, &state->connect_promise);
|
|
1371
|
+
}
|
|
1372
|
+
if (state->scan_pending) {
|
|
1373
|
+
MIK_FreePromise(ctx, &state->scan_promise);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Free event listeners
|
|
1377
|
+
for (auto& v : state->on_connect) {
|
|
1378
|
+
JS_FreeValue(ctx, v);
|
|
1379
|
+
}
|
|
1380
|
+
for (auto& v : state->on_disconnect) {
|
|
1381
|
+
JS_FreeValue(ctx, v);
|
|
1382
|
+
}
|
|
1383
|
+
for (auto& v : state->on_ap_sta_connect) {
|
|
1384
|
+
JS_FreeValue(ctx, v);
|
|
1385
|
+
}
|
|
1386
|
+
for (auto& v : state->on_ap_sta_disconnect) {
|
|
1387
|
+
JS_FreeValue(ctx, v);
|
|
1388
|
+
}
|
|
1389
|
+
for (auto& v : state->on_rssi_low) {
|
|
1390
|
+
JS_FreeValue(ctx, v);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Drain queue
|
|
1394
|
+
MIKWifiEvent evt;
|
|
1395
|
+
while (xQueueReceive(state->event_queue, &evt, 0) == pdTRUE) {
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
s_event_queue = nullptr;
|
|
1399
|
+
vQueueDelete(state->event_queue);
|
|
1400
|
+
delete state;
|
|
1401
|
+
mik__wifi_st(mik_rt) = nullptr;
|
|
1402
|
+
|
|
1403
|
+
/* Fully tear down the WiFi driver so the heap used by the radio stack,
|
|
1404
|
+
* beacon offsets, event handlers, and netif interfaces is returned to
|
|
1405
|
+
* the pool. Without this, repeated runtime lifecycles (e.g. on-device
|
|
1406
|
+
* test suites that create + destroy a runtime per test) eventually hit
|
|
1407
|
+
* `alloc pm_beacon_offset fail` when the heap is too fragmented for
|
|
1408
|
+
* even small internal allocations. */
|
|
1409
|
+
if (s_wifi_initialized) {
|
|
1410
|
+
if (s_wifi_started) {
|
|
1411
|
+
esp_wifi_stop();
|
|
1412
|
+
s_wifi_started = false;
|
|
1413
|
+
}
|
|
1414
|
+
/* ensure_initialized registered these via esp_event_handler_instance_register
|
|
1415
|
+
* with a nullptr context out-param, so we can't use instance_unregister.
|
|
1416
|
+
* esp_event_handler_unregister matches on (base, id, handler fn) instead. */
|
|
1417
|
+
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
|
1418
|
+
mik__wifi_event_handler);
|
|
1419
|
+
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
|
1420
|
+
mik__wifi_event_handler);
|
|
1421
|
+
esp_wifi_deinit();
|
|
1422
|
+
if (s_sta_netif) {
|
|
1423
|
+
esp_netif_destroy_default_wifi(s_sta_netif);
|
|
1424
|
+
s_sta_netif = nullptr;
|
|
1425
|
+
}
|
|
1426
|
+
if (s_ap_netif) {
|
|
1427
|
+
esp_netif_destroy_default_wifi(s_ap_netif);
|
|
1428
|
+
s_ap_netif = nullptr;
|
|
1429
|
+
}
|
|
1430
|
+
s_wifi_initialized = false;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
MIK_REGISTER_MODULE(wifi, "native:wifi", mik__wifi_init, mik__wifi_consume, mik__wifi_destroy)
|