@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.
@@ -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) != pdTRUE) {
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) xQueueSend(s_ble_event_queue, &evt, 0);
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) xQueueSend(s_ble_event_queue, &evt, 0);
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) xQueueSend(s_ble_event_queue, &evt, 0);
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 src_path[512], dst_path[512];
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)->pending.store(true, std::memory_order_relaxed);
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
- xQueueSend(q, &m, portMAX_DELAY);
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
- xQueueSend(q, &m, portMAX_DELAY);
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
- xQueueSend(q, &m, portMAX_DELAY);
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
- xQueueSend(q, &m, portMAX_DELAY);
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;