@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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/bin/idf.py +7 -0
  4. package/chips.json +3 -0
  5. package/cmake.js +9 -0
  6. package/components/mikrojs/CMakeLists.txt +187 -0
  7. package/components/mikrojs/Kconfig +55 -0
  8. package/components/mikrojs/idf_component.yml +6 -0
  9. package/components/mikrojs/include/mem.h +3 -0
  10. package/components/mikrojs/include/mik_color.h +3 -0
  11. package/components/mikrojs/include/mik_http_internal.h +77 -0
  12. package/components/mikrojs/include/mikrojs.h +5 -0
  13. package/components/mikrojs/include/mikrojs_esp32.h +65 -0
  14. package/components/mikrojs/include/private.h +10 -0
  15. package/components/mikrojs/include/utils.h +3 -0
  16. package/components/mikrojs/mik_ble.cpp +1588 -0
  17. package/components/mikrojs/mik_ble_c_shim.c +61 -0
  18. package/components/mikrojs/mik_ble_c_shim.h +37 -0
  19. package/components/mikrojs/mik_config.cpp +167 -0
  20. package/components/mikrojs/mik_deploy.cpp +584 -0
  21. package/components/mikrojs/mik_http.cpp +916 -0
  22. package/components/mikrojs/mik_i2c.cpp +364 -0
  23. package/components/mikrojs/mik_main.cpp +542 -0
  24. package/components/mikrojs/mik_neopixel.cpp +437 -0
  25. package/components/mikrojs/mik_nvs_kv.cpp +219 -0
  26. package/components/mikrojs/mik_pin.cpp +195 -0
  27. package/components/mikrojs/mik_pwm.cpp +525 -0
  28. package/components/mikrojs/mik_recovery.cpp +86 -0
  29. package/components/mikrojs/mik_rtc.cpp +305 -0
  30. package/components/mikrojs/mik_serial_io.cpp +362 -0
  31. package/components/mikrojs/mik_sleep.cpp +226 -0
  32. package/components/mikrojs/mik_sntp.cpp +275 -0
  33. package/components/mikrojs/mik_spi.cpp +330 -0
  34. package/components/mikrojs/mik_uart.cpp +497 -0
  35. package/components/mikrojs/mik_wifi.cpp +1434 -0
  36. package/components/mikrojs/platform_esp32.cpp +192 -0
  37. package/components/mikrojs/test/CMakeLists.txt +32 -0
  38. package/components/mikrojs/test/abort_test.cpp +254 -0
  39. package/components/mikrojs/test/ble_test.cpp +714 -0
  40. package/components/mikrojs/test/fs_js_test.cpp +458 -0
  41. package/components/mikrojs/test/fs_pub_test.cpp +312 -0
  42. package/components/mikrojs/test/http_test.cpp +475 -0
  43. package/components/mikrojs/test/i2c_test.cpp +138 -0
  44. package/components/mikrojs/test/modules_extended_test.cpp +137 -0
  45. package/components/mikrojs/test/modules_test.cpp +131 -0
  46. package/components/mikrojs/test/pins_test.cpp +47 -0
  47. package/components/mikrojs/test/pwm_test.cpp +166 -0
  48. package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
  49. package/components/mikrojs/test/rtc_test.cpp +331 -0
  50. package/components/mikrojs/test/runtime_test.cpp +89 -0
  51. package/components/mikrojs/test/sleep_test.cpp +222 -0
  52. package/components/mikrojs/test/sntp_test.cpp +249 -0
  53. package/components/mikrojs/test/stdio_test.cpp +449 -0
  54. package/components/mikrojs/test/sys_test.cpp +165 -0
  55. package/components/mikrojs/test/text_encoding_test.cpp +224 -0
  56. package/components/mikrojs/test/timers_js_test.cpp +244 -0
  57. package/components/mikrojs/test/timers_test.cpp +79 -0
  58. package/components/mikrojs/test/wifi_test.cpp +599 -0
  59. package/default-app/main/CMakeLists.txt +3 -0
  60. package/default-app/main/main.cpp +5 -0
  61. package/discover.js +77 -0
  62. package/index.d.ts +7 -0
  63. package/index.js +20 -0
  64. package/package.json +61 -0
  65. package/partitions.csv +5 -0
  66. package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
  67. package/prebuilds/esp32/flasher_args.json +24 -0
  68. package/prebuilds/esp32/mikrojs.bin +0 -0
  69. package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
  70. package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
  71. package/prebuilds/esp32c3/flasher_args.json +24 -0
  72. package/prebuilds/esp32c3/mikrojs.bin +0 -0
  73. package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
  74. package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
  75. package/prebuilds/esp32c6/flasher_args.json +24 -0
  76. package/prebuilds/esp32c6/mikrojs.bin +0 -0
  77. package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
  78. package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
  79. package/prebuilds/esp32s3/flasher_args.json +24 -0
  80. package/prebuilds/esp32s3/mikrojs.bin +0 -0
  81. package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
  82. package/project.cmake +101 -0
  83. package/resolve.js +54 -0
  84. package/sdkconfig.defaults +127 -0
  85. package/sdkconfig.defaults.esp32 +8 -0
  86. package/sdkconfig.defaults.esp32c3 +15 -0
  87. package/sdkconfig.defaults.esp32c6 +26 -0
  88. package/sdkconfig.defaults.esp32s3 +22 -0
@@ -0,0 +1,305 @@
1
+ #include <cstring>
2
+
3
+ #include <nanocbor/nanocbor.h>
4
+
5
+ #include "esp_attr.h"
6
+ #include "esp_rom_crc.h"
7
+ #include "mikrojs/cbor_helpers.h"
8
+ #include "mikrojs/errors.h"
9
+ #include "mikrojs/private.h"
10
+ #include "mikrojs/utils.h"
11
+
12
+ #define MIK_RTC_TAG "native:rtc"
13
+ #define MIK_RTC_STORE_SIZE 2048
14
+ #define MIK_RTC_MAGIC 0x4D524A53 /* "MRJS" */
15
+ #define MIK_RTC_MAX_KEY_LEN 255
16
+ #define MIK_RTC_MAX_VAL_LEN 65535
17
+
18
+ /* ── RTC store layout ────────────────────────────────────────────── */
19
+ /* [4B magic] [4B crc32] [2B entry_count] [2B data_len] */
20
+ /* [entries: key_len(1) + key + val_len(2) + value ...] */
21
+
22
+ struct __attribute__((packed)) RtcHeader {
23
+ uint32_t magic;
24
+ uint32_t crc;
25
+ uint16_t entry_count;
26
+ uint16_t data_len;
27
+ };
28
+
29
+ static_assert(sizeof(RtcHeader) == 12, "RtcHeader must be 12 bytes");
30
+
31
+ #define MIK_RTC_DATA_OFFSET sizeof(RtcHeader)
32
+ #define MIK_RTC_DATA_CAPACITY (MIK_RTC_STORE_SIZE - MIK_RTC_DATA_OFFSET)
33
+
34
+ RTC_NOINIT_ATTR static uint8_t s_rtc_store[MIK_RTC_STORE_SIZE];
35
+
36
+ /* ── CRC / validation helpers ────────────────────────────────────── */
37
+
38
+ static uint32_t mik__rtc_compute_crc() {
39
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
40
+ return esp_rom_crc32_le(0, s_rtc_store + MIK_RTC_DATA_OFFSET, hdr->data_len);
41
+ }
42
+
43
+ static bool mik__rtc_is_valid() {
44
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
45
+ if (hdr->magic != MIK_RTC_MAGIC) return false;
46
+ if (hdr->data_len > MIK_RTC_DATA_CAPACITY) return false;
47
+ return hdr->crc == mik__rtc_compute_crc();
48
+ }
49
+
50
+ static void mik__rtc_clear() {
51
+ memset(s_rtc_store, 0, MIK_RTC_STORE_SIZE);
52
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
53
+ hdr->magic = MIK_RTC_MAGIC;
54
+ hdr->crc = mik__rtc_compute_crc();
55
+ }
56
+
57
+ static void mik__rtc_ensure_valid() {
58
+ if (!mik__rtc_is_valid()) {
59
+ mik__rtc_clear();
60
+ }
61
+ }
62
+
63
+ static void mik__rtc_update_crc() {
64
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
65
+ hdr->crc = mik__rtc_compute_crc();
66
+ }
67
+
68
+ /* ── Entry iteration helpers ─────────────────────────────────────── */
69
+
70
+ static uint8_t* mik__rtc_data() {
71
+ return s_rtc_store + MIK_RTC_DATA_OFFSET;
72
+ }
73
+
74
+ /* Returns pointer to entry start, sets out_key_len, out_key, out_val_len, out_val.
75
+ * Returns NULL if no more entries. */
76
+ static uint8_t* mik__rtc_next_entry(uint8_t* pos, uint8_t* end, uint8_t* out_key_len,
77
+ const char** out_key, uint16_t* out_val_len,
78
+ const char** out_val) {
79
+ if (pos >= end) return nullptr;
80
+ if (pos + 1 > end) return nullptr;
81
+
82
+ *out_key_len = pos[0];
83
+ if (pos + 1 + *out_key_len + 2 > end) return nullptr;
84
+
85
+ *out_key = reinterpret_cast<const char*>(pos + 1);
86
+
87
+ uint8_t* vl = pos + 1 + *out_key_len;
88
+ *out_val_len = static_cast<uint16_t>(vl[0]) | (static_cast<uint16_t>(vl[1]) << 8);
89
+
90
+ if (vl + 2 + *out_val_len > end) return nullptr;
91
+
92
+ *out_val = reinterpret_cast<const char*>(vl + 2);
93
+ return vl + 2 + *out_val_len;
94
+ }
95
+
96
+ /* Find an entry by key. Returns pointer to its start, or NULL. */
97
+ static uint8_t* mik__rtc_find(const char* key, uint8_t key_len, uint8_t** out_next) {
98
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
99
+ uint8_t* data = mik__rtc_data();
100
+ uint8_t* end = data + hdr->data_len;
101
+ uint8_t* pos = data;
102
+
103
+ while (pos < end) {
104
+ uint8_t ek_len;
105
+ const char* ek;
106
+ uint16_t ev_len;
107
+ const char* ev;
108
+ uint8_t* entry_start = pos;
109
+ uint8_t* next = mik__rtc_next_entry(pos, end, &ek_len, &ek, &ev_len, &ev);
110
+ if (!next) break;
111
+
112
+ if (ek_len == key_len && memcmp(ek, key, key_len) == 0) {
113
+ if (out_next) *out_next = next;
114
+ return entry_start;
115
+ }
116
+ pos = next;
117
+ }
118
+
119
+ if (out_next) *out_next = nullptr;
120
+ return nullptr;
121
+ }
122
+
123
+ static size_t mik__rtc_entry_size(uint8_t key_len, uint16_t val_len) {
124
+ return 1 + key_len + 2 + val_len;
125
+ }
126
+
127
+ /* ── JS functions ────────────────────────────────────────────────── */
128
+
129
+ static JSValue mik__rtc_set(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
130
+ mik__rtc_ensure_valid();
131
+
132
+ const char* key = JS_ToCString(ctx, argv[0]);
133
+ if (!key) return JS_EXCEPTION;
134
+ size_t key_len = strlen(key);
135
+
136
+ if (key_len == 0 || key_len > MIK_RTC_MAX_KEY_LEN) {
137
+ JS_FreeCString(ctx, key);
138
+ return mik__result_err(ctx, MIK_ERR_KV_INVALID_KEY, 0,
139
+ "RTC key length must be 1-%d bytes", MIK_RTC_MAX_KEY_LEN);
140
+ }
141
+
142
+ /* CBOR-encode the value (two-pass: measure then encode) */
143
+ nanocbor_encoder_t enc;
144
+ nanocbor_encoder_init(&enc, nullptr, 0);
145
+ if (mik__cbor_encode_value(ctx, &enc, argv[1], 0) < 0) {
146
+ JS_FreeCString(ctx, key);
147
+ return mik__result_err(ctx, MIK_ERR_KV_ENCODE, 0, "RTC value is not CBOR-encodable");
148
+ }
149
+ size_t val_len = nanocbor_encoded_len(&enc);
150
+
151
+ if (val_len > MIK_RTC_MAX_VAL_LEN) {
152
+ JS_FreeCString(ctx, key);
153
+ return mik__result_err(ctx, MIK_ERR_KV_TOO_LARGE, 0,
154
+ "RTC value length exceeds %d bytes", MIK_RTC_MAX_VAL_LEN);
155
+ }
156
+
157
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
158
+ uint8_t* data = mik__rtc_data();
159
+
160
+ /* Remove existing entry if present */
161
+ uint8_t* next = nullptr;
162
+ uint8_t* found = mik__rtc_find(key, static_cast<uint8_t>(key_len), &next);
163
+ if (found && next) {
164
+ uint8_t* end = data + hdr->data_len;
165
+ size_t tail = end - next;
166
+ memmove(found, next, tail);
167
+ hdr->data_len -= static_cast<uint16_t>(next - found);
168
+ hdr->entry_count--;
169
+ }
170
+
171
+ /* Check space */
172
+ size_t needed = mik__rtc_entry_size(static_cast<uint8_t>(key_len), static_cast<uint16_t>(val_len));
173
+ if (hdr->data_len + needed > MIK_RTC_DATA_CAPACITY) {
174
+ JS_FreeCString(ctx, key);
175
+ return mik__result_err(ctx, MIK_ERR_KV_STORAGE_FULL, 0,
176
+ "RTC memory full (need %zu bytes, %zu available)", needed,
177
+ MIK_RTC_DATA_CAPACITY - hdr->data_len);
178
+ }
179
+
180
+ /* Append new entry: encode CBOR directly into the RTC store */
181
+ uint8_t* write_pos = data + hdr->data_len;
182
+ write_pos[0] = static_cast<uint8_t>(key_len);
183
+ memcpy(write_pos + 1, key, key_len);
184
+ write_pos[1 + key_len] = static_cast<uint8_t>(val_len & 0xFF);
185
+ write_pos[1 + key_len + 1] = static_cast<uint8_t>((val_len >> 8) & 0xFF);
186
+
187
+ nanocbor_encoder_init(&enc, write_pos + 1 + key_len + 2, val_len);
188
+ mik__cbor_encode_value(ctx, &enc, argv[1], 0);
189
+
190
+ hdr->data_len += static_cast<uint16_t>(needed);
191
+ hdr->entry_count++;
192
+ mik__rtc_update_crc();
193
+
194
+ JS_FreeCString(ctx, key);
195
+ return mik__result_ok_void(ctx);
196
+ }
197
+
198
+ static JSValue mik__rtc_get(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
199
+ mik__rtc_ensure_valid();
200
+
201
+ const char* key = JS_ToCString(ctx, argv[0]);
202
+ if (!key) return JS_EXCEPTION;
203
+ size_t key_len = strlen(key);
204
+
205
+ if (key_len == 0 || key_len > MIK_RTC_MAX_KEY_LEN) {
206
+ JS_FreeCString(ctx, key);
207
+ return JS_UNDEFINED;
208
+ }
209
+
210
+ uint8_t* found = mik__rtc_find(key, static_cast<uint8_t>(key_len), nullptr);
211
+ JS_FreeCString(ctx, key);
212
+
213
+ if (!found) return JS_UNDEFINED;
214
+
215
+ /* Parse the entry to extract the CBOR value */
216
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
217
+ uint8_t* data = mik__rtc_data();
218
+ uint8_t* end = data + hdr->data_len;
219
+
220
+ uint8_t ek_len;
221
+ const char* ek;
222
+ uint16_t ev_len;
223
+ const char* ev;
224
+ mik__rtc_next_entry(found, end, &ek_len, &ek, &ev_len, &ev);
225
+
226
+ /* Decode CBOR bytes back into a JS value */
227
+ nanocbor_value_t decoder;
228
+ nanocbor_decoder_init(&decoder, reinterpret_cast<const uint8_t*>(ev), ev_len);
229
+ JSValue result = mik__cbor_decode_value(ctx, &decoder, 0);
230
+ if (JS_IsException(result)) return JS_UNDEFINED;
231
+ return result;
232
+ }
233
+
234
+ static JSValue mik__rtc_remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
235
+ mik__rtc_ensure_valid();
236
+
237
+ const char* key = JS_ToCString(ctx, argv[0]);
238
+ if (!key) return JS_EXCEPTION;
239
+ size_t key_len = strlen(key);
240
+
241
+ if (key_len == 0 || key_len > MIK_RTC_MAX_KEY_LEN) {
242
+ JS_FreeCString(ctx, key);
243
+ return JS_NewBool(ctx, false);
244
+ }
245
+
246
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
247
+ uint8_t* data = mik__rtc_data();
248
+
249
+ uint8_t* next = nullptr;
250
+ uint8_t* found = mik__rtc_find(key, static_cast<uint8_t>(key_len), &next);
251
+ JS_FreeCString(ctx, key);
252
+
253
+ if (!found || !next) return JS_NewBool(ctx, false);
254
+
255
+ uint8_t* end = data + hdr->data_len;
256
+ size_t tail = end - next;
257
+ memmove(found, next, tail);
258
+ hdr->data_len -= static_cast<uint16_t>(next - found);
259
+ hdr->entry_count--;
260
+ mik__rtc_update_crc();
261
+
262
+ return JS_NewBool(ctx, true);
263
+ }
264
+
265
+ static JSValue mik__rtc_js_clear(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
266
+ mik__rtc_clear();
267
+ return JS_UNDEFINED;
268
+ }
269
+
270
+ static JSValue mik__rtc_info(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
271
+ mik__rtc_ensure_valid();
272
+
273
+ auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
274
+ JSValue obj = JS_NewObject(ctx);
275
+ JS_SetPropertyStr(ctx, obj, "used", JS_NewInt32(ctx, hdr->data_len));
276
+ JS_SetPropertyStr(ctx, obj, "total", JS_NewInt32(ctx, MIK_RTC_DATA_CAPACITY));
277
+ JS_SetPropertyStr(ctx, obj, "entries", JS_NewInt32(ctx, hdr->entry_count));
278
+ return obj;
279
+ }
280
+
281
+ /* ── Module init ─────────────────────────────────────────────────── */
282
+
283
+ static int mik__rtc_module_init(JSContext* ctx, JSModuleDef* m) {
284
+ JS_SetModuleExport(ctx, m, "set", JS_NewCFunction(ctx, mik__rtc_set, "set", 2));
285
+ JS_SetModuleExport(ctx, m, "get", JS_NewCFunction(ctx, mik__rtc_get, "get", 1));
286
+ JS_SetModuleExport(ctx, m, "remove", JS_NewCFunction(ctx, mik__rtc_remove, "remove", 1));
287
+ JS_SetModuleExport(ctx, m, "clear", JS_NewCFunction(ctx, mik__rtc_js_clear, "clear", 0));
288
+ JS_SetModuleExport(ctx, m, "info", JS_NewCFunction(ctx, mik__rtc_info, "info", 0));
289
+ return 0;
290
+ }
291
+
292
+ /* ── Public API ──────────────────────────────────────────────────── */
293
+
294
+ static JSModuleDef* mik__rtc_init(JSContext* ctx) {
295
+ JSModuleDef* m = JS_NewCModule(ctx, "native:rtc", mik__rtc_module_init);
296
+ if (!m) return nullptr;
297
+ JS_AddModuleExport(ctx, m, "set");
298
+ JS_AddModuleExport(ctx, m, "get");
299
+ JS_AddModuleExport(ctx, m, "remove");
300
+ JS_AddModuleExport(ctx, m, "clear");
301
+ JS_AddModuleExport(ctx, m, "info");
302
+ return m;
303
+ }
304
+
305
+ MIK_REGISTER_MODULE(rtc, "native:rtc", mik__rtc_init, nullptr, nullptr)
@@ -0,0 +1,362 @@
1
+ #include <cerrno>
2
+ #include <stdio.h>
3
+ #include <string.h>
4
+
5
+ #include "driver/uart.h"
6
+ #include "esp_intr_alloc.h"
7
+ #include "esp_log.h"
8
+ #include "freertos/FreeRTOS.h"
9
+ #include "freertos/task.h"
10
+ #include "nvs.h"
11
+ #include "nvs_flash.h"
12
+ #include "mikrojs/mikrojs.h"
13
+ #include "mikrojs/platform.h"
14
+ #include "mikrojs_esp32.h"
15
+ #include "soc/soc_caps.h"
16
+ #include "soc/uart_pins.h"
17
+
18
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
19
+ #include "driver/usb_serial_jtag.h"
20
+ /* mikrojs owns the USB-Serial/JTAG peripheral directly. Leaving
21
+ * CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y would have ESP-IDF register a
22
+ * VFS and write to the TX FIFO from stdio, racing our driver ISR. */
23
+ #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
24
+ #error "Set CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=n (see sdkconfig.defaults.esp32*)"
25
+ #endif
26
+ #endif
27
+
28
+ /* ── Console I/O ────────────────────────────────────────────────────
29
+ *
30
+ * Talks to UART0 and (where supported) the USB-Serial/JTAG peripheral
31
+ * directly through the ESP-IDF driver API, deliberately skipping the
32
+ * VFS / newlib stdio integration. Rationale:
33
+ *
34
+ * - With `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`, ESP-IDF's startup
35
+ * code registers a USB-JTAG VFS, hooks it into newlib's stdio, and
36
+ * installs the driver before app_main() runs. On boards that cold-
37
+ * boot from battery, that early VFS/newlib setup has been observed
38
+ * to tip the supply past the brown-out threshold and lock the
39
+ * chip. Micropython avoids this by talking to the USB-JTAG
40
+ * hardware directly, never touching VFS; we do the same here but
41
+ * use the stable public driver API (`usb_serial_jtag_*_bytes`)
42
+ * instead of register-level LL calls, so the code survives IDF
43
+ * major-version bumps without rework.
44
+ *
45
+ * - sdkconfig keeps `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=n` and
46
+ * `CONFIG_ESP_CONSOLE_UART_DEFAULT=y`. That makes UART0 the target
47
+ * of ESP_LOG/printf, but log output is already filtered to NONE in
48
+ * production. We manage both consoles ourselves.
49
+ *
50
+ * - The active console latches to whichever interface receives the
51
+ * first byte from the host CLI (same UX as before).
52
+ */
53
+
54
+ /* UART0 is always present on chips without USB-Serial/JTAG (plain
55
+ * ESP32). On chips with USJ, it's installed only when the
56
+ * CONFIG_MIKROJS_CONSOLE_FALLBACK_UART Kconfig is enabled (default
57
+ * n). Enable it to get the auto-detect dev UX on boards where a
58
+ * USB-TTL adapter is sometimes used instead of the native USB port;
59
+ * costs ~4 KB DRAM + a UART ISR in IRAM. */
60
+ #if !SOC_USB_SERIAL_JTAG_SUPPORTED || CONFIG_MIKROJS_CONSOLE_FALLBACK_UART
61
+ #define MIK_CONSOLE_HAS_UART 1
62
+ #else
63
+ #define MIK_CONSOLE_HAS_UART 0
64
+ #endif
65
+
66
+ static enum { CONSOLE_UART, CONSOLE_USB_SERIAL_JTAG } s_console =
67
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
68
+ CONSOLE_USB_SERIAL_JTAG;
69
+ #else
70
+ CONSOLE_UART;
71
+ #endif
72
+
73
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
74
+ static bool s_usj_installed = false;
75
+ #endif
76
+
77
+ void mik__console_init(void) {
78
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
79
+ /* Install USB-Serial/JTAG via the public driver API only. No VFS
80
+ * registration, no `fileno(stdin)`, no newlib integration — that's
81
+ * the whole point of this path.
82
+ *
83
+ * Both rings are sized to fit the largest single TLV frame plus a
84
+ * little headroom for the next frame's header to land alongside it.
85
+ *
86
+ * RX (host → device): the largest single command frame is a
87
+ * CMD_DEPLOY_PUT_CHUNK (≤ 2 KB body + 5 B header). Deploy PUTs no
88
+ * longer carry the file body in one frame — they're streamed as
89
+ * begin + N chunks with per-chunk MSG_OK pacing — so this size is
90
+ * a function of the chunk size, not of the largest deployable file.
91
+ * 4 KB gives ~2× headroom over a single chunk so the next command
92
+ * can be queued behind it.
93
+ *
94
+ * TX (device → host): bumped from the IDF default (256 B) to 2 KB
95
+ * so MSG_READY resends during the boot-handshake window don't fill
96
+ * the ring before the CLI's TTY starts draining. At 256 B the ring
97
+ * fills after ~3 MSG_READY sends, then subsequent writes (including
98
+ * the MSG_OK response to the first CMD_RUNTIME_PAUSE) stall long
99
+ * enough to blow past the CLI's 10 s pause timeout and force a
100
+ * restart-and-retry on every `mikro dev`. 2 KB is still small
101
+ * enough not to meaningfully dent RAM for display-heavy apps. */
102
+ usb_serial_jtag_driver_config_t usj_cfg = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
103
+ usj_cfg.rx_buffer_size = 4096;
104
+ usj_cfg.tx_buffer_size = 2048;
105
+ s_usj_installed = (usb_serial_jtag_driver_install(&usj_cfg) == ESP_OK);
106
+ #endif
107
+
108
+ #if MIK_CONSOLE_HAS_UART
109
+ /* UART0 for the primary (ESP32) or fallback (USJ-capable chips with
110
+ * CONFIG_MIKROJS_CONSOLE_FALLBACK_UART=y) console path. Configured
111
+ * explicitly because ESP_CONSOLE_UART_DEFAULT's startup code may
112
+ * not leave the hardware in the state we want for raw read/write. */
113
+ uart_config_t uart_config = {};
114
+ uart_config.baud_rate = 115200;
115
+ uart_config.data_bits = UART_DATA_8_BITS;
116
+ uart_config.parity = UART_PARITY_DISABLE;
117
+ uart_config.stop_bits = UART_STOP_BITS_1;
118
+ uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
119
+ uart_config.source_clk = UART_SCLK_DEFAULT;
120
+ uart_param_config(UART_NUM_0, &uart_config);
121
+ uart_set_pin(UART_NUM_0, U0TXD_GPIO_NUM, U0RXD_GPIO_NUM,
122
+ UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
123
+ /* IRAM ISR keeps UART alive during flash writes (LittleFS deploys). */
124
+ uart_driver_install(UART_NUM_0, 4096, 0, 0, NULL, ESP_INTR_FLAG_IRAM);
125
+ #endif
126
+ }
127
+
128
+ int mik__console_read(void* buf, size_t len) {
129
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
130
+ if (s_console == CONSOLE_USB_SERIAL_JTAG) {
131
+ if (s_usj_installed) {
132
+ int n = usb_serial_jtag_read_bytes(buf, len, 0);
133
+ if (n > 0) return n;
134
+ }
135
+ #if MIK_CONSOLE_HAS_UART
136
+ /* Auto-detect: probe UART0 too so the first byte on either
137
+ * interface latches the active console. Once latched to UART
138
+ * we stop probing USJ to avoid interleaving input streams. */
139
+ int n = uart_read_bytes(UART_NUM_0, buf, len, 0);
140
+ if (n > 0) {
141
+ s_console = CONSOLE_UART;
142
+ return n;
143
+ }
144
+ #endif
145
+ /* The REPL protocol treats 0 as EOF, so map "no data
146
+ * available right now" to -1/EAGAIN like the old VFS
147
+ * path did. */
148
+ errno = EAGAIN;
149
+ return -1;
150
+ }
151
+ #endif
152
+ #if MIK_CONSOLE_HAS_UART
153
+ int n = uart_read_bytes(UART_NUM_0, buf, len, 0);
154
+ if (n == 0) {
155
+ errno = EAGAIN;
156
+ return -1;
157
+ }
158
+ return n;
159
+ #else
160
+ errno = EAGAIN;
161
+ return -1;
162
+ #endif
163
+ }
164
+
165
+ int mik__console_write(const void* buf, size_t len) {
166
+ #if SOC_USB_SERIAL_JTAG_SUPPORTED
167
+ if (s_console == CONSOLE_USB_SERIAL_JTAG) {
168
+ /* No host attached: the ring buffer would fill and block writes
169
+ * forever. Drop silently so boot-time TLV sends (MSG_READY etc.)
170
+ * don't stall on bus/battery-only power. */
171
+ if (!s_usj_installed || !usb_serial_jtag_is_connected()) {
172
+ return (int)len;
173
+ }
174
+ /* Chunk writes below the TX ring size. `usb_serial_jtag_write_bytes`
175
+ * will not accept more than fits contiguously in the ring, so asking
176
+ * for (say) 257 bytes of a 256-byte ring returns 0 every iteration
177
+ * regardless of how fast the host drains. 64 sits well under any
178
+ * reasonable ring config.
179
+ *
180
+ * Bail after ~10 s of zero-progress iterations (host gone AWOL) so
181
+ * the runtime can't hang forever. */
182
+ const size_t max_chunk = 64;
183
+ const uint8_t* p = (const uint8_t*)buf;
184
+ size_t remaining = len;
185
+ int no_progress_ticks = 0;
186
+ while (remaining > 0) {
187
+ if (!usb_serial_jtag_is_connected()) return (int)len;
188
+ size_t chunk = remaining < max_chunk ? remaining : max_chunk;
189
+ int n = usb_serial_jtag_write_bytes(p, chunk, pdMS_TO_TICKS(200));
190
+ if (n > 0) {
191
+ p += n;
192
+ remaining -= (size_t)n;
193
+ no_progress_ticks = 0;
194
+ } else if (++no_progress_ticks > 50) {
195
+ return (int)len;
196
+ }
197
+ }
198
+ return (int)len;
199
+ }
200
+ #endif
201
+ #if MIK_CONSOLE_HAS_UART
202
+ return uart_write_bytes(UART_NUM_0, buf, len);
203
+ #else
204
+ /* No console backend installed — drop the bytes. */
205
+ return (int)len;
206
+ #endif
207
+ }
208
+
209
+ /* ── Binary serial I/O ──────────────────────────────────────────── */
210
+
211
+ void mik__serial_binary_begin_no_echo(void) {
212
+ esp_log_level_set("*", ESP_LOG_NONE);
213
+ /* No line-ending translation to disable: we bypass VFS entirely,
214
+ * so `\n` passes through unchanged on both USJ and UART paths. */
215
+ }
216
+
217
+ /* ── NVS helpers ─────────────────────────────────────────────────── */
218
+
219
+ const char* MIK__NVS_NS_ENV = "mik.env";
220
+ const char* MIK__NVS_NS_SEC = "mik.sec";
221
+
222
+ void mik__nvs_clear_namespace(const char* ns) {
223
+ nvs_handle_t handle;
224
+ if (nvs_open(ns, NVS_READWRITE, &handle) == ESP_OK) {
225
+ nvs_erase_all(handle);
226
+ nvs_commit(handle);
227
+ nvs_close(handle);
228
+ }
229
+ }
230
+
231
+ bool mik__nvs_put_string(const char* ns, const char* key, const char* value) {
232
+ nvs_handle_t handle;
233
+ esp_err_t err = nvs_open(ns, NVS_READWRITE, &handle);
234
+ if (err != ESP_OK) return false;
235
+
236
+ err = nvs_set_str(handle, key, value);
237
+ if (err == ESP_OK) {
238
+ err = nvs_commit(handle);
239
+ }
240
+ nvs_close(handle);
241
+ return err == ESP_OK;
242
+ }
243
+
244
+ /* Write only if the value differs from what's already stored. `out_changed`
245
+ * is set to true when the stored value ends up different than before, false
246
+ * if the existing value already matched. Returns false on I/O failure. */
247
+ bool mik__nvs_put_string_if_diff(const char* ns, const char* key, const char* value,
248
+ bool* out_changed) {
249
+ nvs_handle_t handle;
250
+ esp_err_t err = nvs_open(ns, NVS_READWRITE, &handle);
251
+ if (err != ESP_OK) return false;
252
+
253
+ bool changed = true;
254
+ size_t existing_len = 0;
255
+ if (nvs_get_str(handle, key, nullptr, &existing_len) == ESP_OK) {
256
+ size_t new_len = strlen(value);
257
+ if (existing_len == new_len + 1) {
258
+ char buf[512];
259
+ if (existing_len <= sizeof(buf) &&
260
+ nvs_get_str(handle, key, buf, &existing_len) == ESP_OK) {
261
+ changed = memcmp(buf, value, new_len) != 0;
262
+ }
263
+ }
264
+ }
265
+
266
+ if (changed) {
267
+ err = nvs_set_str(handle, key, value);
268
+ if (err == ESP_OK) {
269
+ err = nvs_commit(handle);
270
+ }
271
+ }
272
+ nvs_close(handle);
273
+
274
+ if (out_changed) *out_changed = changed;
275
+ return err == ESP_OK;
276
+ }
277
+
278
+ bool mik__nvs_is_secret(const char* key) {
279
+ nvs_handle_t handle;
280
+ if (nvs_open(MIK__NVS_NS_SEC, NVS_READONLY, &handle) != ESP_OK) {
281
+ return false;
282
+ }
283
+ uint8_t val = 0;
284
+ esp_err_t err = nvs_get_u8(handle, key, &val);
285
+ nvs_close(handle);
286
+ return err == ESP_OK && val == 1;
287
+ }
288
+
289
+ bool mik__nvs_set_secret_flag(const char* key, bool secret) {
290
+ nvs_handle_t handle;
291
+ if (nvs_open(MIK__NVS_NS_SEC, NVS_READWRITE, &handle) != ESP_OK) {
292
+ return false;
293
+ }
294
+ esp_err_t err = secret ? nvs_set_u8(handle, key, 1) : nvs_erase_key(handle, key);
295
+ /* Erasing a key that wasn't there is a no-op success for our purposes. */
296
+ if (err == ESP_ERR_NVS_NOT_FOUND) err = ESP_OK;
297
+ if (err == ESP_OK) err = nvs_commit(handle);
298
+ nvs_close(handle);
299
+ return err == ESP_OK;
300
+ }
301
+
302
+ /* Sets the env var. `out_changed` reports whether the stored value or secret
303
+ * flag ended up differing from before (true on first insert, on value change,
304
+ * or on secret-flag flip). Returns false on I/O failure (env value or secret
305
+ * flag); `out_changed` is left untouched when returning false. */
306
+ bool mik__nvs_env_set(const char* key, const char* value, bool secret, bool* out_changed) {
307
+ bool was_secret = mik__nvs_is_secret(key);
308
+ bool value_changed = false;
309
+ if (!mik__nvs_put_string_if_diff(MIK__NVS_NS_ENV, key, value, &value_changed)) {
310
+ return false;
311
+ }
312
+ if (was_secret != secret && !mik__nvs_set_secret_flag(key, secret)) {
313
+ return false;
314
+ }
315
+ if (out_changed) *out_changed = value_changed || (was_secret != secret);
316
+ return true;
317
+ }
318
+
319
+ /* Erases the env var. Returns true if the key existed before the call. */
320
+ bool mik__nvs_env_delete(const char* key) {
321
+ bool existed = false;
322
+ nvs_handle_t handle;
323
+ if (nvs_open(MIK__NVS_NS_ENV, NVS_READWRITE, &handle) == ESP_OK) {
324
+ size_t len = 0;
325
+ if (nvs_get_str(handle, key, nullptr, &len) == ESP_OK) {
326
+ existed = true;
327
+ }
328
+ nvs_erase_key(handle, key);
329
+ nvs_commit(handle);
330
+ nvs_close(handle);
331
+ }
332
+ mik__nvs_set_secret_flag(key, false);
333
+ return existed;
334
+ }
335
+
336
+ /* ── NVS → MIK_SetEnvVar bridge ─────────────────────────────────── */
337
+
338
+ void MIK_LoadEnvFromNVS(MIKRuntime* mik_rt) {
339
+ nvs_handle_t handle;
340
+ if (nvs_open(MIK__NVS_NS_ENV, NVS_READONLY, &handle) != ESP_OK) {
341
+ return;
342
+ }
343
+
344
+ nvs_iterator_t it = NULL;
345
+ esp_err_t err = nvs_entry_find_in_handle(handle, NVS_TYPE_STR, &it);
346
+ while (err == ESP_OK && it != NULL) {
347
+ nvs_entry_info_t info;
348
+ nvs_entry_info(it, &info);
349
+
350
+ size_t len = 0;
351
+ if (nvs_get_str(handle, info.key, NULL, &len) == ESP_OK) {
352
+ char buf[512];
353
+ if (len <= sizeof(buf) && nvs_get_str(handle, info.key, buf, &len) == ESP_OK) {
354
+ MIK_SetEnvVar(mik_rt, info.key, buf);
355
+ }
356
+ }
357
+
358
+ err = nvs_entry_next(&it);
359
+ }
360
+ nvs_release_iterator(it);
361
+ nvs_close(handle);
362
+ }