@mikrojs/firmware 0.21.1-next.20260927212749 → 0.22.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.
- package/components/mikrojs/CMakeLists.txt +2 -2
- package/components/mikrojs/Kconfig +73 -1
- package/components/mikrojs/include/mikrojs_esp32.h +4 -3
- package/components/mikrojs/mik_ble.cpp +15 -4
- package/components/mikrojs/mik_ble_c_shim.c +10 -0
- package/components/mikrojs/mik_ble_c_shim.h +9 -0
- package/components/mikrojs/mik_deploy.cpp +92 -10
- package/components/mikrojs/mik_gpio.cpp +6 -1
- package/components/mikrojs/mik_http.cpp +10 -4
- package/components/mikrojs/mik_http_server.cpp +2 -0
- package/components/mikrojs/mik_i2s.cpp +17 -0
- package/components/mikrojs/mik_logfile.cpp +0 -7
- package/components/mikrojs/mik_main.cpp +130 -98
- package/components/mikrojs/mik_pwm.cpp +28 -1
- package/components/mikrojs/mik_serial_io.cpp +35 -23
- package/components/mikrojs/mik_sntp.cpp +2 -0
- package/components/mikrojs/mik_uart.cpp +41 -0
- package/components/mikrojs/mik_wifi.cpp +1 -0
- package/components/mikrojs/platform_esp32.cpp +76 -2
- package/dist/boards.d.ts +73 -12
- package/dist/boards.d.ts.map +1 -1
- package/dist/boards.js +137 -60
- package/dist/boards.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/sdkconfig.defaults +2 -0
- package/components/mikrojs/include/mem.h +0 -3
- package/components/mikrojs/include/mik_color.h +0 -3
|
@@ -49,14 +49,13 @@ endif()
|
|
|
49
49
|
# idf_component.yml.
|
|
50
50
|
idf_component_register(
|
|
51
51
|
INCLUDE_DIRS "include" "${MIK_INCLUDE_DIR}"
|
|
52
|
-
REQUIRES bt littlefs esp_adc esp_app_format esp_driver_gpio esp_driver_i2c esp_driver_i2s esp_driver_ledc esp_driver_rmt esp_driver_spi esp_psram esp_timer esp_http_client esp_http_server esp-tls esp_netif esp_event esp_wifi nvs_flash esp_driver_uart esp_driver_usb_serial_jtag spi_flash
|
|
52
|
+
REQUIRES bt littlefs esp_adc esp_app_format esp_driver_gpio esp_driver_i2c esp_driver_i2s esp_driver_ledc esp_driver_rmt esp_driver_spi esp_pm esp_psram esp_timer esp_http_client esp_http_server esp-tls esp_netif esp_event esp_wifi nvs_flash esp_driver_uart esp_driver_usb_serial_jtag spi_flash
|
|
53
53
|
SRCS
|
|
54
54
|
# QuickJS engine
|
|
55
55
|
${QUICKJS_SOURCES}
|
|
56
56
|
# Standalone library portable sources
|
|
57
57
|
"${MIK_SRC_DIR}/cutils_compat.c"
|
|
58
58
|
"${MIK_SRC_DIR}/builtins.cpp"
|
|
59
|
-
"${MIK_SRC_DIR}/eval_bytecode.cpp"
|
|
60
59
|
"${MIK_SRC_DIR}/mik_abort.cpp"
|
|
61
60
|
"${MIK_SRC_DIR}/mik_http_client.cpp"
|
|
62
61
|
"${MIK_SRC_DIR}/mik_wifi_client.cpp"
|
|
@@ -74,6 +73,7 @@ idf_component_register(
|
|
|
74
73
|
"${MIK_SRC_DIR}/mik_text_encoding.cpp"
|
|
75
74
|
"${MIK_SRC_DIR}/mik_cbor.cpp"
|
|
76
75
|
"${MIK_SRC_DIR}/mik_schema.cpp"
|
|
76
|
+
"${MIK_SRC_DIR}/mik_test.cpp"
|
|
77
77
|
"${MIK_SRC_DIR}/mik_sys_codec.cpp"
|
|
78
78
|
"${MIK_SRC_DIR}/mik_result.cpp"
|
|
79
79
|
"${MIK_SRC_DIR}/mik_udp.cpp"
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
menu "mikrojs boot"
|
|
2
|
+
|
|
3
|
+
config MIKROJS_RECOVERY_WINDOW_MS
|
|
4
|
+
int "Recovery window at boot (ms)"
|
|
5
|
+
default 500
|
|
6
|
+
range 0 5000
|
|
7
|
+
help
|
|
8
|
+
How long the firmware listens on the console at the start of
|
|
9
|
+
every boot, before it runs the app, for the recovery trigger:
|
|
10
|
+
the sync sequence that `mikro deploy --recover`, `mikro clean
|
|
11
|
+
--recover` and `mikro console --recover` send after resetting
|
|
12
|
+
the chip, or a second press of the reset button inside the
|
|
13
|
+
window. Either one skips the app and drops to the REPL, which
|
|
14
|
+
is how you replace a crash-looping app.
|
|
15
|
+
|
|
16
|
+
Every boot pays the whole window at full power, including
|
|
17
|
+
every wake from deep sleep, since a wake is a reboot. The
|
|
18
|
+
double reset needs a window long enough to press reset twice,
|
|
19
|
+
and --recover needs it to outlast the host's reconnect after
|
|
20
|
+
the reset, so a short value is in practice the same as 0. At
|
|
21
|
+
0 the device boots straight into the app, and --recover and
|
|
22
|
+
the double reset stop working. A crash-looping app then has
|
|
23
|
+
to be erased over USB from the ROM bootloader (`mikro erase`,
|
|
24
|
+
then `mikro flash`). Keep the default unless you have another
|
|
25
|
+
way back into a broken device.
|
|
26
|
+
|
|
27
|
+
endmenu
|
|
28
|
+
|
|
1
29
|
menu "mikrojs console"
|
|
2
30
|
|
|
3
31
|
config MIKROJS_CONSOLE_FALLBACK_UART
|
|
@@ -22,6 +50,49 @@ menu "mikrojs console"
|
|
|
22
50
|
|
|
23
51
|
endmenu
|
|
24
52
|
|
|
53
|
+
menu "mikrojs power"
|
|
54
|
+
|
|
55
|
+
config MIKROJS_AUTO_LIGHT_SLEEP
|
|
56
|
+
bool "Enter light sleep automatically when idle (experimental)"
|
|
57
|
+
default n
|
|
58
|
+
# select skips PM_ENABLE's own conditions, so repeat them here.
|
|
59
|
+
depends on SOC_PM_SUPPORTED && !FREERTOS_SMP
|
|
60
|
+
depends on ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED
|
|
61
|
+
depends on !MIKROJS_CONSOLE_FALLBACK_UART
|
|
62
|
+
select PM_ENABLE
|
|
63
|
+
select PM_DFS_INIT_AUTO
|
|
64
|
+
select FREERTOS_USE_TICKLESS_IDLE
|
|
65
|
+
select USJ_NO_AUTO_LS_ON_CONNECTION
|
|
66
|
+
help
|
|
67
|
+
When enabled, the chip enters light sleep whenever no task needs
|
|
68
|
+
to run and no driver holds a power lock, and wakes for the next
|
|
69
|
+
timer. It also turns on ESP-IDF power management with frequency
|
|
70
|
+
scaling (DFS), which lowers the CPU clock while the app waits.
|
|
71
|
+
|
|
72
|
+
MIK_Main switches light sleep on. A custom main that does not call
|
|
73
|
+
MIK_Main gets frequency scaling and tickless idle but no light
|
|
74
|
+
sleep, unless it calls esp_pm_configure itself.
|
|
75
|
+
|
|
76
|
+
The USB-Serial/JTAG console stops in light sleep, so
|
|
77
|
+
USJ_NO_AUTO_LS_ON_CONNECTION keeps the chip awake while a USB host
|
|
78
|
+
is connected: the console, REPL and deploy work as before, and the
|
|
79
|
+
savings start once the host is gone.
|
|
80
|
+
|
|
81
|
+
An open Pwm, Uart, NeoPixel or I2s handle keeps the chip awake, and
|
|
82
|
+
I2C and SPI transfers do too. WiFi sleeps between beacons in the
|
|
83
|
+
"min" and "max" power save modes. BLE keeps the chip awake while
|
|
84
|
+
the controller is enabled.
|
|
85
|
+
|
|
86
|
+
Known limits: GPIO edges do not wake the chip, so an edge during
|
|
87
|
+
sleep can be missed. A USB host plugged in while the chip sleeps
|
|
88
|
+
can fail to enumerate; reset the board. On the ESP32-S3 the CPU
|
|
89
|
+
state saved across sleep takes up to about 17 KB of internal RAM.
|
|
90
|
+
|
|
91
|
+
Not available with a UART console (the ESP32, or
|
|
92
|
+
MIKROJS_CONSOLE_FALLBACK_UART), since UART RX stops in light sleep.
|
|
93
|
+
|
|
94
|
+
endmenu
|
|
95
|
+
|
|
25
96
|
menu "mikrojs WiFi"
|
|
26
97
|
|
|
27
98
|
config MIKROJS_WIFI
|
|
@@ -70,7 +141,8 @@ menu "mikrojs runtime memory"
|
|
|
70
141
|
|
|
71
142
|
Defaults on for chips with PSRAM. Disable if your workload
|
|
72
143
|
is JS-execution-bound and you can fit the JS heap in
|
|
73
|
-
internal SRAM. No effect on chips without PSRAM
|
|
144
|
+
internal SRAM. No effect on chips without PSRAM, or when no
|
|
145
|
+
PSRAM chip is found at boot.
|
|
74
146
|
|
|
75
147
|
endmenu
|
|
76
148
|
|
|
@@ -78,6 +78,10 @@ void mik__console_init(void);
|
|
|
78
78
|
/* Read from the active console. Returns bytes read, 0 if no data, or -1. */
|
|
79
79
|
int mik__console_read(void* buf, size_t len);
|
|
80
80
|
|
|
81
|
+
/* MIK_Wake for interrupt handlers (platform_esp32.cpp). IRAM-resident, so a
|
|
82
|
+
* handler installed with ESP_INTR_FLAG_IRAM may call it with the flash cache off. */
|
|
83
|
+
void MIK_WakeFromISR(void);
|
|
84
|
+
|
|
81
85
|
/* Write to the active console. Returns bytes written. */
|
|
82
86
|
int mik__console_write(const void* buf, size_t len);
|
|
83
87
|
|
|
@@ -94,7 +98,6 @@ void mik__console_set_tap(mik_console_tap_fn fn);
|
|
|
94
98
|
typedef struct MIKConfig MIKConfig;
|
|
95
99
|
void mik_logfile_init(const MIKConfig* config);
|
|
96
100
|
void mik_logfile_close(void);
|
|
97
|
-
void mik_logfile_flush(void);
|
|
98
101
|
/* Release/restore the underlying FILE* so the host can read the file
|
|
99
102
|
* without contention. Output between suspend/resume is dropped. No-op
|
|
100
103
|
* when file logging is disabled. */
|
|
@@ -140,8 +143,6 @@ void mik__serial_io_attach_usb(void);
|
|
|
140
143
|
extern const char* MIK__NVS_NS_ENV; /* env var values */
|
|
141
144
|
extern const char* MIK__NVS_NS_SEC; /* secret-flag markers (u8) */
|
|
142
145
|
/* ENV_FLAG_SECRET is defined in mikrojs/private.h as MIK_ENV_FLAG_SECRET */
|
|
143
|
-
void mik__nvs_clear_namespace(const char* ns);
|
|
144
|
-
bool mik__nvs_put_string(const char* ns, const char* key, const char* value);
|
|
145
146
|
bool mik__nvs_put_string_if_diff(const char* ns, const char* key, const char* value,
|
|
146
147
|
bool* out_changed);
|
|
147
148
|
bool mik__nvs_is_secret(const char* key);
|
|
@@ -577,7 +577,9 @@ static int mik__ble_gatt_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
|
|
577
577
|
evt.attr_handle = attr_handle;
|
|
578
578
|
evt.write_data = pool_buf;
|
|
579
579
|
evt.write_data_len = len;
|
|
580
|
-
if (xQueueSend(s_ble_event_queue, &evt, 0)
|
|
580
|
+
if (xQueueSend(s_ble_event_queue, &evt, 0) == pdTRUE) {
|
|
581
|
+
MIK_Wake();
|
|
582
|
+
} else {
|
|
581
583
|
/* Queue full — return the buffer to the pool. */
|
|
582
584
|
mik__ble_pool_free(pool_buf);
|
|
583
585
|
ESP_LOGW(MIK_BLE_TAG, "event queue full, dropping write event");
|
|
@@ -641,7 +643,10 @@ static int mik__ble_gap_event_cb(struct ble_gap_event* event, void* arg) {
|
|
|
641
643
|
evt.mtu = mtu;
|
|
642
644
|
/* Connection formed — NimBLE stopped advertising automatically. */
|
|
643
645
|
s_advertising = false;
|
|
644
|
-
if (s_ble_event_queue)
|
|
646
|
+
if (s_ble_event_queue) {
|
|
647
|
+
xQueueSend(s_ble_event_queue, &evt, 0);
|
|
648
|
+
MIK_Wake();
|
|
649
|
+
}
|
|
645
650
|
return 0;
|
|
646
651
|
}
|
|
647
652
|
case BLE_GAP_EVENT_DISCONNECT: {
|
|
@@ -669,7 +674,10 @@ static int mik__ble_gap_event_cb(struct ble_gap_event* event, void* arg) {
|
|
|
669
674
|
memcpy(evt.peer_addr, peer_addr, 6);
|
|
670
675
|
evt.mtu = cached_mtu;
|
|
671
676
|
evt.disconnect_reason = static_cast<uint8_t>(event->disconnect.reason & 0xff);
|
|
672
|
-
if (s_ble_event_queue)
|
|
677
|
+
if (s_ble_event_queue) {
|
|
678
|
+
xQueueSend(s_ble_event_queue, &evt, 0);
|
|
679
|
+
MIK_Wake();
|
|
680
|
+
}
|
|
673
681
|
return 0;
|
|
674
682
|
}
|
|
675
683
|
case BLE_GAP_EVENT_MTU: {
|
|
@@ -684,7 +692,10 @@ static int mik__ble_gap_event_cb(struct ble_gap_event* event, void* arg) {
|
|
|
684
692
|
evt.type = MIK_BLE_EVT_MTU;
|
|
685
693
|
evt.conn_handle = event->mtu.conn_handle;
|
|
686
694
|
evt.mtu = event->mtu.value;
|
|
687
|
-
if (s_ble_event_queue)
|
|
695
|
+
if (s_ble_event_queue) {
|
|
696
|
+
xQueueSend(s_ble_event_queue, &evt, 0);
|
|
697
|
+
MIK_Wake();
|
|
698
|
+
}
|
|
688
699
|
return 0;
|
|
689
700
|
}
|
|
690
701
|
case BLE_GAP_EVENT_SUBSCRIBE: {
|
|
@@ -59,3 +59,13 @@ int mik_ble_controller_deinit(void) {
|
|
|
59
59
|
if (status == ESP_BT_CONTROLLER_STATUS_IDLE) return 0;
|
|
60
60
|
return (int)esp_bt_controller_deinit();
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/* ── Boot-time memory release ──────────────────────────────────────── */
|
|
64
|
+
|
|
65
|
+
#if CONFIG_IDF_TARGET_ESP32
|
|
66
|
+
/* Only the original ESP32 has a classic-BT controller region to give back
|
|
67
|
+
* (SOC_MEM_BT_EM_BREDR_START..SOC_MEM_BT_EM_BREDR_REAL_END). */
|
|
68
|
+
int mik_ble_release_classic_bt_mem(void) {
|
|
69
|
+
return (int)esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
|
70
|
+
}
|
|
71
|
+
#endif
|
|
@@ -30,6 +30,15 @@ int mik_ble_controller_disable(void);
|
|
|
30
30
|
*/
|
|
31
31
|
int mik_ble_controller_deinit(void);
|
|
32
32
|
|
|
33
|
+
/* ── Boot-time memory release ──────────────────────────────────────── */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Release the classic-BT controller memory to the heap. Original ESP32
|
|
37
|
+
* only; call before the controller is initialized. Returns 0 on success
|
|
38
|
+
* or a non-zero esp_err_t code on failure.
|
|
39
|
+
*/
|
|
40
|
+
int mik_ble_release_classic_bt_mem(void);
|
|
41
|
+
|
|
33
42
|
#ifdef __cplusplus
|
|
34
43
|
}
|
|
35
44
|
#endif
|
|
@@ -265,6 +265,16 @@ static const char* copy_file(const char* src, const char* dst) {
|
|
|
265
265
|
return failed;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
/* Stage a live file for the next commit. Returns NULL on success, otherwise
|
|
269
|
+
* the step that failed, with errno set. */
|
|
270
|
+
static const char* keep_file(const char* name) {
|
|
271
|
+
char src_path[512], dst_path[512];
|
|
272
|
+
snprintf(src_path, sizeof(src_path), "%s%s", FS_BASE, name);
|
|
273
|
+
snprintf(dst_path, sizeof(dst_path), "%s%s", DEPLOY_TMP, name);
|
|
274
|
+
if (!make_parent_dir(dst_path)) return "create directory failed";
|
|
275
|
+
return copy_file(src_path, dst_path);
|
|
276
|
+
}
|
|
277
|
+
|
|
268
278
|
/* ── Deploy recovery ─────────────────────────────────────────────── */
|
|
269
279
|
|
|
270
280
|
void MIK_DeployRecover(void) {
|
|
@@ -414,16 +424,7 @@ bool mik__handle_deploy_command(MIKReplTransport* transport, uint8_t cmd_type,
|
|
|
414
424
|
if (!mik__proto_read_exact(transport, name, name_len)) return false;
|
|
415
425
|
name[name_len] = '\0';
|
|
416
426
|
|
|
417
|
-
char
|
|
418
|
-
snprintf(src_path, sizeof(src_path), "%s%s", FS_BASE, name);
|
|
419
|
-
snprintf(dst_path, sizeof(dst_path), "%s%s", DEPLOY_TMP, name);
|
|
420
|
-
|
|
421
|
-
if (!make_parent_dir(dst_path)) {
|
|
422
|
-
send_errno(transport, "create directory failed", errno);
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const char* failed = copy_file(src_path, dst_path);
|
|
427
|
+
const char* failed = keep_file(name);
|
|
427
428
|
if (failed) {
|
|
428
429
|
send_errno(transport, failed, errno);
|
|
429
430
|
} else {
|
|
@@ -432,6 +433,87 @@ bool mik__handle_deploy_command(MIKReplTransport* transport, uint8_t cmd_type,
|
|
|
432
433
|
return true;
|
|
433
434
|
}
|
|
434
435
|
|
|
436
|
+
case MIK_CMD_DEPLOY_KEEP_MANY: {
|
|
437
|
+
/* Payload: repeated u16le name_len | name. Each file is staged as
|
|
438
|
+
* KEEP stages it. The first failure ends the batch: the rest of
|
|
439
|
+
* the payload is dropped and the error names the file. */
|
|
440
|
+
uint32_t consumed = 0;
|
|
441
|
+
while (consumed < payload_len) {
|
|
442
|
+
if (payload_len - consumed < 2) {
|
|
443
|
+
mik__proto_drain(transport, payload_len - consumed);
|
|
444
|
+
mik__proto_send_err(transport, "keep list truncated");
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
uint8_t nl[2];
|
|
448
|
+
if (!mik__proto_read_exact(transport, nl, 2)) return false;
|
|
449
|
+
consumed += 2;
|
|
450
|
+
uint16_t name_len = nl[0] | (nl[1] << 8);
|
|
451
|
+
|
|
452
|
+
char name[256];
|
|
453
|
+
if (name_len >= sizeof(name) || name_len > payload_len - consumed) {
|
|
454
|
+
mik__proto_drain(transport, payload_len - consumed);
|
|
455
|
+
mik__proto_send_err(transport, "keep filename too long");
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (!mik__proto_read_exact(transport, name, name_len)) return false;
|
|
459
|
+
consumed += name_len;
|
|
460
|
+
name[name_len] = '\0';
|
|
461
|
+
|
|
462
|
+
const char* failed = keep_file(name);
|
|
463
|
+
if (failed) {
|
|
464
|
+
int err = errno;
|
|
465
|
+
mik__proto_drain(transport, payload_len - consumed);
|
|
466
|
+
char msg[384];
|
|
467
|
+
snprintf(msg, sizeof(msg), "%s: %s: %s", name, failed, strerror(err));
|
|
468
|
+
mik__proto_send_err(transport, msg);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
mik__proto_send_ok(transport);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
case MIK_CMD_DEPLOY_CHECKSUM_LIST: {
|
|
477
|
+
/* Reply: u16le file_count | the manifest lines whose file is on
|
|
478
|
+
* disk, so the host never KEEPs a file a stale manifest still
|
|
479
|
+
* names (see CHECKSUM). file_count counts every manifest entry. */
|
|
480
|
+
mik__proto_drain(transport, payload_len);
|
|
481
|
+
const ChecksumsManifest& m = s_deploy_manifest;
|
|
482
|
+
auto* reply = static_cast<uint8_t*>(malloc(2 + m.len));
|
|
483
|
+
if (!reply) {
|
|
484
|
+
mik__proto_send_err(transport, "out of memory");
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
reply[0] = static_cast<uint8_t>(s_deploy_manifest_files & 0xff);
|
|
488
|
+
reply[1] = static_cast<uint8_t>(s_deploy_manifest_files >> 8);
|
|
489
|
+
size_t out = 2;
|
|
490
|
+
if (m.data) {
|
|
491
|
+
const char* p = m.data;
|
|
492
|
+
const char* end = m.data + m.len;
|
|
493
|
+
while (p < end) {
|
|
494
|
+
const char* nl = static_cast<const char*>(memchr(p, '\n', end - p));
|
|
495
|
+
const char* next = nl ? nl + 1 : end;
|
|
496
|
+
size_t entry_len = static_cast<size_t>((nl ? nl : end) - p);
|
|
497
|
+
/* "<64 hex> <name>", the name no longer than KEEP accepts. */
|
|
498
|
+
if (entry_len >= 67 && entry_len < 66 + 256 && p[0] != '#') {
|
|
499
|
+
char path[512];
|
|
500
|
+
snprintf(path, sizeof(path), "%s%.*s", FS_BASE,
|
|
501
|
+
static_cast<int>(entry_len - 66), p + 66);
|
|
502
|
+
feed_watchdog();
|
|
503
|
+
if (path_exists(path)) {
|
|
504
|
+
size_t line_len = static_cast<size_t>(next - p);
|
|
505
|
+
memcpy(reply + out, p, line_len);
|
|
506
|
+
out += line_len;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
p = next;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
mik__proto_send(transport, MIK_MSG_OK, reply, out);
|
|
513
|
+
free(reply);
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
|
|
435
517
|
case MIK_CMD_DEPLOY_PUT: {
|
|
436
518
|
/* Begin a streaming PUT.
|
|
437
519
|
* Payload: u16le name_len | name | u32le total_size
|
|
@@ -476,7 +476,11 @@ static JSValue js_digital_out_write(JSContext* ctx, JSValueConst this_val, int a
|
|
|
476
476
|
/* ── DigitalIn ───────────────────────────────────────────────────── */
|
|
477
477
|
|
|
478
478
|
static IRAM_ATTR void mik__gpio_isr(void* arg) {
|
|
479
|
-
static_cast<MIKGpioState*>(arg)
|
|
479
|
+
auto* s = static_cast<MIKGpioState*>(arg);
|
|
480
|
+
/* One wake per batch of edges: the consumer reads the level once for them all. */
|
|
481
|
+
if (s->pending.load(std::memory_order_relaxed)) return;
|
|
482
|
+
s->pending.store(true, std::memory_order_relaxed);
|
|
483
|
+
MIK_WakeFromISR();
|
|
480
484
|
}
|
|
481
485
|
|
|
482
486
|
static JSValue js_digital_in_read(JSContext* ctx, JSValueConst this_val, int argc,
|
|
@@ -681,6 +685,7 @@ void mik__gpio_wake_done(int gpio) {
|
|
|
681
685
|
gpio_set_intr_type(num, GPIO_INTR_ANYEDGE);
|
|
682
686
|
/* The level may have changed while edges were off; let the consumer compare. */
|
|
683
687
|
s->pending.store(true, std::memory_order_relaxed);
|
|
688
|
+
MIK_Wake();
|
|
684
689
|
/* Re-adding re-enables the interrupt on the service's core. */
|
|
685
690
|
gpio_isr_handler_add(num, mik__gpio_isr, s);
|
|
686
691
|
return;
|
|
@@ -118,6 +118,12 @@ static esp_err_t mik__http_event_handler(esp_http_client_event_t* evt) {
|
|
|
118
118
|
return ESP_OK;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/* Queues a message for the loop consumer and wakes the loop to take it. */
|
|
122
|
+
static void mik__post(QueueHandle_t q, const MIKHttpMsg* m) {
|
|
123
|
+
xQueueSend(q, m, portMAX_DELAY);
|
|
124
|
+
MIK_Wake();
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
static void mik__post_headers(QueueHandle_t q, uint32_t id, int status,
|
|
122
128
|
MIKHttpHeader* headers, size_t header_count) {
|
|
123
129
|
MIKHttpMsg m = {};
|
|
@@ -126,7 +132,7 @@ static void mik__post_headers(QueueHandle_t q, uint32_t id, int status,
|
|
|
126
132
|
m.status = status;
|
|
127
133
|
m.headers = headers;
|
|
128
134
|
m.header_count = header_count;
|
|
129
|
-
|
|
135
|
+
mik__post(q, &m);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
static void mik__post_chunk(QueueHandle_t q, uint32_t id, uint8_t* data, size_t len) {
|
|
@@ -135,14 +141,14 @@ static void mik__post_chunk(QueueHandle_t q, uint32_t id, uint8_t* data, size_t
|
|
|
135
141
|
m.kind = MIK_HTTP_MSG_CHUNK;
|
|
136
142
|
m.chunk_data = data;
|
|
137
143
|
m.chunk_len = len;
|
|
138
|
-
|
|
144
|
+
mik__post(q, &m);
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
static void mik__post_end(QueueHandle_t q, uint32_t id) {
|
|
142
148
|
MIKHttpMsg m = {};
|
|
143
149
|
m.id = id;
|
|
144
150
|
m.kind = MIK_HTTP_MSG_END;
|
|
145
|
-
|
|
151
|
+
mik__post(q, &m);
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
static void mik__post_error(QueueHandle_t q, uint32_t id, const char* msg, bool cancelled) {
|
|
@@ -154,7 +160,7 @@ static void mik__post_error(QueueHandle_t q, uint32_t id, const char* msg, bool
|
|
|
154
160
|
/* If strdup fails under OOM, the downstream consumer treats NULL as "" but
|
|
155
161
|
* the JS side then sees an empty error message. Leave NULL here — the
|
|
156
162
|
* empty-string fallback in mik__error_msg_value keeps the flow intact. */
|
|
157
|
-
|
|
163
|
+
mik__post(q, &m);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
static bool mik__task_cancelled(MIKHttpTaskArgs* args) {
|
|
@@ -110,6 +110,7 @@ static void mik__hs_free_exchange(MIKHsExchange* ex) {
|
|
|
110
110
|
static void mik__hs_post_ack(QueueHandle_t q, MIKHsExchange* ex, bool ok) {
|
|
111
111
|
MIKHsAck a = {ex, ok};
|
|
112
112
|
xQueueSend(q, &a, portMAX_DELAY);
|
|
113
|
+
MIK_Wake();
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
static void mik__hs_set_head(httpd_req_t* req, MIKHsExchange* ex) {
|
|
@@ -183,6 +184,7 @@ static esp_err_t mik__hs_handler(httpd_req_t* req) {
|
|
|
183
184
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, nullptr);
|
|
184
185
|
return ESP_OK;
|
|
185
186
|
}
|
|
187
|
+
MIK_Wake();
|
|
186
188
|
|
|
187
189
|
/* Drive the response from JS commands. httpd_stop() can't complete until
|
|
188
190
|
* this handler returns, so stop() sets `stopping` before calling it. */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include <cstring>
|
|
3
3
|
|
|
4
4
|
#include "driver/i2s_std.h"
|
|
5
|
+
#include "esp_attr.h"
|
|
5
6
|
/* soc_caps.h has no I2S controller count in IDF 6.1; the HAL header does. */
|
|
6
7
|
#include "hal/i2s_ll.h"
|
|
7
8
|
#include "soc/soc_caps.h"
|
|
@@ -229,6 +230,16 @@ static void mik__i2s_del_channels(MIKI2sState* s) {
|
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
|
|
233
|
+
/* I2S ISR: a DMA buffer went out. With a chunk queued, the loop consumer can
|
|
234
|
+
* push the next piece now instead of at its wait's cap. */
|
|
235
|
+
static bool IRAM_ATTR mik__i2s_on_sent(i2s_chan_handle_t handle, i2s_event_data_t* event,
|
|
236
|
+
void* user_ctx) {
|
|
237
|
+
(void)handle;
|
|
238
|
+
(void)event;
|
|
239
|
+
if (static_cast<MIKI2sState*>(user_ctx)->tx_count > 0) MIK_WakeFromISR();
|
|
240
|
+
return false; // the wake yields itself
|
|
241
|
+
}
|
|
242
|
+
|
|
232
243
|
/* Creates, configures and enables the channels. */
|
|
233
244
|
static esp_err_t mik__i2s_start(MIKI2sState* s, uint32_t sample_rate, int dma_frames,
|
|
234
245
|
int dma_buffers) {
|
|
@@ -291,6 +302,12 @@ static esp_err_t mik__i2s_start(MIKI2sState* s, uint32_t sample_rate, int dma_fr
|
|
|
291
302
|
return err;
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
if (s->tx_chan) {
|
|
306
|
+
i2s_event_callbacks_t cbs = {};
|
|
307
|
+
cbs.on_sent = mik__i2s_on_sent;
|
|
308
|
+
if ((err = i2s_channel_register_event_callback(s->tx_chan, &cbs, s)) != ESP_OK)
|
|
309
|
+
return err;
|
|
310
|
+
}
|
|
294
311
|
if (s->tx_chan && (err = i2s_channel_enable(s->tx_chan)) != ESP_OK) return err;
|
|
295
312
|
if (s->rx_chan && (err = i2s_channel_enable(s->rx_chan)) != ESP_OK) return err;
|
|
296
313
|
return ESP_OK;
|
|
@@ -281,13 +281,6 @@ void mik_logfile_reset(void) {
|
|
|
281
281
|
xSemaphoreGive(s_mtx);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
void mik_logfile_flush(void) {
|
|
285
|
-
if (!s_mtx || !s_file) return;
|
|
286
|
-
if (xSemaphoreTake(s_mtx, pdMS_TO_TICKS(50)) != pdTRUE) return;
|
|
287
|
-
flush_to_flash();
|
|
288
|
-
xSemaphoreGive(s_mtx);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
284
|
void mik_logfile_close(void) {
|
|
292
285
|
if (!s_mtx) return;
|
|
293
286
|
if (xSemaphoreTake(s_mtx, pdMS_TO_TICKS(100)) != pdTRUE) return;
|