@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,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
|
+
}
|