@mikrojs/firmware 0.21.0 → 0.21.1-next.20260926222438

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 (75) hide show
  1. package/README.md +5 -5
  2. package/bin/mikro-fw.js +7 -0
  3. package/components/mikrojs/CMakeLists.txt +87 -86
  4. package/components/mikrojs/mik_gpio.cpp +1 -1
  5. package/components/mikrojs/mik_i2c.cpp +4 -4
  6. package/components/mikrojs/mik_i2s.cpp +5 -5
  7. package/components/mikrojs/mik_main.cpp +2 -5
  8. package/components/mikrojs/mik_neopixel.cpp +1 -1
  9. package/components/mikrojs/mik_nvs_kv.cpp +5 -5
  10. package/components/mikrojs/mik_ota_env.cpp +2 -3
  11. package/components/mikrojs/mik_pwm.cpp +1 -1
  12. package/components/mikrojs/mik_rtc.cpp +16 -12
  13. package/components/mikrojs/mik_spi.cpp +5 -5
  14. package/components/mikrojs/mik_uart.cpp +8 -8
  15. package/components/mikrojs/mik_wifi.cpp +1 -3
  16. package/components/mikrojs/test/nvs_kv_test.cpp +21 -0
  17. package/components/mikrojs/test/rtc_test.cpp +74 -3
  18. package/dist/boards.d.ts +80 -0
  19. package/dist/boards.d.ts.map +1 -0
  20. package/dist/boards.js +263 -0
  21. package/dist/boards.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.d.ts.map +1 -0
  24. package/dist/cli.js +75 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +7 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/inputs.d.ts +16 -0
  31. package/dist/inputs.d.ts.map +1 -0
  32. package/dist/inputs.js +97 -0
  33. package/dist/inputs.js.map +1 -0
  34. package/dist/manifest.d.ts +44 -0
  35. package/dist/manifest.d.ts.map +1 -0
  36. package/dist/manifest.js +164 -0
  37. package/dist/manifest.js.map +1 -0
  38. package/{prebuilds → dist-fw}/esp32-generic/bootloader/bootloader.bin +0 -0
  39. package/dist-fw/esp32-generic/firmware.json +1 -0
  40. package/{prebuilds → dist-fw}/esp32-generic/mikrojs.bin +0 -0
  41. package/{prebuilds → dist-fw}/esp32c3-generic/bootloader/bootloader.bin +0 -0
  42. package/dist-fw/esp32c3-generic/firmware.json +1 -0
  43. package/{prebuilds → dist-fw}/esp32c3-generic/mikrojs.bin +0 -0
  44. package/{prebuilds → dist-fw}/esp32c5-generic/bootloader/bootloader.bin +0 -0
  45. package/dist-fw/esp32c5-generic/firmware.json +1 -0
  46. package/{prebuilds → dist-fw}/esp32c5-generic/mikrojs.bin +0 -0
  47. package/{prebuilds → dist-fw}/esp32c6-generic/bootloader/bootloader.bin +0 -0
  48. package/dist-fw/esp32c6-generic/firmware.json +1 -0
  49. package/{prebuilds → dist-fw}/esp32c6-generic/mikrojs.bin +0 -0
  50. package/{prebuilds → dist-fw}/esp32s3-generic/bootloader/bootloader.bin +0 -0
  51. package/dist-fw/esp32s3-generic/firmware.json +1 -0
  52. package/{prebuilds → dist-fw}/esp32s3-generic/mikrojs.bin +0 -0
  53. package/package.json +51 -15
  54. package/project.cmake +74 -46
  55. package/resolve.cmake +36 -0
  56. package/cmake.js +0 -9
  57. package/discover.js +0 -82
  58. package/index.d.ts +0 -9
  59. package/index.js +0 -41
  60. package/prebuilds/esp32-generic/firmware.json +0 -1
  61. package/prebuilds/esp32c3-generic/firmware.json +0 -1
  62. package/prebuilds/esp32c5-generic/firmware.json +0 -1
  63. package/prebuilds/esp32c6-generic/firmware.json +0 -1
  64. package/prebuilds/esp32s3-generic/firmware.json +0 -1
  65. package/resolve.js +0 -64
  66. /package/{prebuilds → dist-fw}/esp32-generic/flasher_args.json +0 -0
  67. /package/{prebuilds → dist-fw}/esp32-generic/partition_table/partition-table.bin +0 -0
  68. /package/{prebuilds → dist-fw}/esp32c3-generic/flasher_args.json +0 -0
  69. /package/{prebuilds → dist-fw}/esp32c3-generic/partition_table/partition-table.bin +0 -0
  70. /package/{prebuilds → dist-fw}/esp32c5-generic/flasher_args.json +0 -0
  71. /package/{prebuilds → dist-fw}/esp32c5-generic/partition_table/partition-table.bin +0 -0
  72. /package/{prebuilds → dist-fw}/esp32c6-generic/flasher_args.json +0 -0
  73. /package/{prebuilds → dist-fw}/esp32c6-generic/partition_table/partition-table.bin +0 -0
  74. /package/{prebuilds → dist-fw}/esp32s3-generic/flasher_args.json +0 -0
  75. /package/{prebuilds → dist-fw}/esp32s3-generic/partition_table/partition-table.bin +0 -0
package/README.md CHANGED
@@ -4,18 +4,18 @@ ESP32 firmware package for Mikro.js. Provides the ESP-IDF integration, build sys
4
4
 
5
5
  ## What's included
6
6
 
7
- - **`project.cmake`** - CMake module that integrates with ESP-IDF. Handles component discovery, SDK config merging, and partition table defaults.
7
+ - **`project.cmake`** - CMake module that integrates with ESP-IDF. Turns the native modules that the project lists into ESP-IDF components, merges SDK config, and applies partition table defaults.
8
8
  - **`components/mikrojs/`** - ESP-IDF component that compiles the Mikro.js runtime, QuickJS engine, and platform-specific C modules.
9
9
  - **Default configs** - `sdkconfig.defaults` and `partitions.csv` for common setups.
10
10
 
11
11
  ## Usage
12
12
 
13
- A custom firmware project depends on this package and includes `project.cmake` (resolved via `resolve.js`, see the docs below for the CMakeLists boilerplate):
13
+ A custom firmware project depends on this package and includes its `project.cmake`, whose path the package's `mikro-fw` bin prints (see the docs below for the CMakeLists boilerplate):
14
14
 
15
15
  ```
16
16
  my-firmware/
17
- ├── package.json # depends on @mikrojs/firmware
18
- ├── CMakeLists.txt # resolves and includes project.cmake via resolve.js
17
+ ├── package.json # depends on @mikrojs/firmware and mikro
18
+ ├── CMakeLists.txt # includes project.cmake from @mikrojs/firmware
19
19
  └── main/ # optional: omit it and the package's default main
20
20
  ├── CMakeLists.txt # (which calls MIK_Main()) is used automatically
21
21
  └── main.cpp
@@ -28,4 +28,4 @@ See the [Custom Firmware](https://mikrojs.dev/develop/custom-firmware) docs for
28
28
  - Node.js >= 24
29
29
  - ESP-IDF >= 6.1 (installed via [EIM](https://docs.espressif.com/projects/idf-im-ui/en/latest/))
30
30
 
31
- To build, activate ESP-IDF in your shell first: run `eim select` and source the activation script it prints, then use `idf.py` as usual.
31
+ To build, run `pn mikro idf build` in the project. `mikro idf` passes its arguments to `idf.py`, through EIM when ESP-IDF is not active in the shell. Plain `idf.py` works too, in a shell where ESP-IDF is active.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // In the Mikro.js repository, run the TypeScript source, so a firmware build
3
+ // never runs a stale dist/. A published package has only dist/.
4
+ import {existsSync} from 'node:fs'
5
+
6
+ const src = new URL('../src/cli.ts', import.meta.url)
7
+ await import(existsSync(src) ? src.href : '../dist/cli.js')
@@ -1,4 +1,4 @@
1
- # ── Resolve package paths via Node.js ────────────────────────────────
1
+ # ── Package paths ────────────────────────────────────────────────────
2
2
  # MIKROJS_PROJECT_DIR is the directory containing package.json and node_modules.
3
3
  # Defaults to CMAKE_SOURCE_DIR which works for both:
4
4
  # - Monorepo: CMAKE_SOURCE_DIR is esp32/ (has package.json with workspace deps)
@@ -7,28 +7,18 @@ if(NOT DEFINED MIKROJS_PROJECT_DIR)
7
7
  set(MIKROJS_PROJECT_DIR "${CMAKE_SOURCE_DIR}")
8
8
  endif()
9
9
 
10
- # resolve.js lives in the firmware package root, two levels up from this component
11
- set(_MIK_RESOLVE "${CMAKE_CURRENT_LIST_DIR}/../../resolve.js")
12
-
13
- execute_process(
14
- COMMAND node ${_MIK_RESOLVE} quickjs
15
- OUTPUT_VARIABLE QUICKJS_CMAKE_PATH
16
- OUTPUT_STRIP_TRAILING_WHITESPACE
17
- )
18
- execute_process(
19
- COMMAND node ${_MIK_RESOLVE} native
20
- OUTPUT_VARIABLE _NATIVE_JSON
21
- OUTPUT_STRIP_TRAILING_WHITESPACE
22
- )
23
- # Parse JSON output
24
- string(JSON MIK_INCLUDE_DIR GET "${_NATIVE_JSON}" "include")
25
- string(JSON MIK_SRC_DIR GET "${_NATIVE_JSON}" "src")
26
- string(JSON MIK_RUNTIME_DIR GET "${_NATIVE_JSON}" "runtime")
27
- string(JSON MIK_SCRIPTS_DIR GET "${_NATIVE_JSON}" "scripts")
28
- string(JSON MIK_BYTECODE_CMAKE GET "${_NATIVE_JSON}" "bytecodeCmake")
29
-
30
- # Include quickjs.cmake for QUICKJS_SOURCES, QUICKJS_INCLUDE_DIR, etc.
31
- include("${QUICKJS_CMAKE_PATH}")
10
+ # The MIK_* paths come from resolve.cmake, which project.cmake includes.
11
+ # ESP-IDF first runs this file in a separate script-mode pass that only
12
+ # collects REQUIRES, where they are unset. That pass reads no SRCS, so only
13
+ # the include below needs a guard; REQUIRES must never depend on them.
14
+ if(MIK_SRC_DIR)
15
+ # Include quickjs.cmake for QUICKJS_SOURCES, QUICKJS_INCLUDE_DIR, etc.
16
+ include("${MIK_QUICKJS_CMAKE}")
17
+ elseif(NOT CMAKE_BUILD_EARLY_EXPANSION)
18
+ message(FATAL_ERROR
19
+ "mikrojs: the @mikrojs/native paths are not set. Include @mikrojs/firmware's "
20
+ "project.cmake in the project's CMakeLists.txt, before project().")
21
+ endif()
32
22
 
33
23
  # nanocbor lives inside the native package
34
24
  set(MIK_NANOCBOR_DIR "${MIK_SRC_DIR}/../deps/nanocbor")
@@ -155,15 +145,12 @@ set_source_files_properties(${QUICKJS_SOURCES} PROPERTIES
155
145
  COMPILE_OPTIONS "-Wno-unused-but-set-variable")
156
146
 
157
147
  # ── Board and version metadata ──────────────────────────────────────
158
- execute_process(
159
- COMMAND node ${_MIK_RESOLVE} version
160
- OUTPUT_VARIABLE _MIK_VERSION
161
- OUTPUT_STRIP_TRAILING_WHITESPACE
162
- )
148
+ file(READ "${CMAKE_CURRENT_LIST_DIR}/../../package.json" _MIK_PACKAGE)
149
+ string(JSON _MIK_VERSION GET "${_MIK_PACKAGE}" version)
163
150
  target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_FW_VERSION=\"${_MIK_VERSION}\"")
164
151
  # Core runtime owns the bare `native:mikro/*` namespace. MIK_PACKAGE_NAME makes
165
152
  # MIK_REGISTER_MODULE enforce that every native module here is named
166
- # "native:mikro/<module>"; driver/board packages set their own package name.
153
+ # "native:mikro/<module>"; a package's native module sets its package name.
167
154
  target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_PACKAGE_NAME=\"mikro\"")
168
155
  # The version is read at configure time, so configure must re-run when the
169
156
  # firmware package version changes — otherwise an incremental idf.py build
@@ -171,24 +158,53 @@ target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_PACKAGE_NAME=\"mikro\""
171
158
  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
172
159
  "${CMAKE_CURRENT_LIST_DIR}/../../package.json")
173
160
 
174
- # Firmware identity: the consuming project's package.json name, reported in
175
- # MSG_READY as `fw`. The CLI only auto-flashes its bundled prebuilt over a
176
- # device whose identity matches the prebuilt's own recorded name, so custom
177
- # firmware is protected from the auto-reflash without any project marker.
178
- # Absent (no package.json, e.g. on-device test apps) the field is omitted.
179
- execute_process(
180
- COMMAND node ${_MIK_RESOLVE} projectName ${CMAKE_SOURCE_DIR}
181
- OUTPUT_VARIABLE _MIK_FW_NAME
182
- OUTPUT_STRIP_TRAILING_WHITESPACE
183
- )
184
- # STREQUAL, not if(_MIK_FW_NAME): a project named "0", "off", or "false"
185
- # would read as a false constant and silently lose its identity.
186
- if(NOT _MIK_FW_NAME STREQUAL "")
187
- target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_FW_NAME=\"${_MIK_FW_NAME}\"")
188
- endif()
189
- if(EXISTS "${CMAKE_SOURCE_DIR}/package.json")
161
+ # Board name and description: MIKROJS_BOARD_NAME and MIKROJS_BOARD_DESCRIPTION
162
+ # (resolved by project.cmake), else the name and description in the nearest
163
+ # package.json at or above the project (the app's, for a firmware folder inside
164
+ # an app). The name is compiled in and reported as sys.board.name, and as
165
+ # `board` and `fw` in MSG_READY. The CLI only auto-flashes its bundled image over
166
+ # a device whose `fw` matches that image's name, so other firmware is never
167
+ # replaced by accident. Without a name, `fw` is omitted and the board is
168
+ # <chip>-generic.
169
+ set(_MIK_NAME "${_MIK_BOARD_NAME}")
170
+ set(_MIK_DESCRIPTION "${_MIK_BOARD_DESCRIPTION}")
171
+ set(_MIK_FW_DIR "${CMAKE_SOURCE_DIR}")
172
+ while(NOT EXISTS "${_MIK_FW_DIR}/package.json")
173
+ get_filename_component(_MIK_FW_PARENT "${_MIK_FW_DIR}" DIRECTORY)
174
+ if(_MIK_FW_PARENT STREQUAL _MIK_FW_DIR)
175
+ break()
176
+ endif()
177
+ set(_MIK_FW_DIR "${_MIK_FW_PARENT}")
178
+ endwhile()
179
+ if(EXISTS "${_MIK_FW_DIR}/package.json")
180
+ file(READ "${_MIK_FW_DIR}/package.json" _MIK_PROJECT_PACKAGE)
181
+ foreach(_field IN ITEMS name description)
182
+ string(TOUPPER "${_field}" _FIELD)
183
+ if(_MIK_${_FIELD} STREQUAL "")
184
+ string(JSON _MIK_${_FIELD} ERROR_VARIABLE _MIK_FIELD_ERROR
185
+ GET "${_MIK_PROJECT_PACKAGE}" ${_field})
186
+ if(_MIK_FIELD_ERROR)
187
+ set(_MIK_${_FIELD} "")
188
+ endif()
189
+ endif()
190
+ endforeach()
190
191
  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
191
- "${CMAKE_SOURCE_DIR}/package.json")
192
+ "${_MIK_FW_DIR}/package.json")
193
+ endif()
194
+ # STREQUAL, not if(_MIK_NAME): a project named "0", "off", or "false" would read
195
+ # as a false constant and silently lose its name.
196
+ if(NOT _MIK_NAME STREQUAL "")
197
+ # The device keeps the name in a 64-byte buffer, and a registry rejects a
198
+ # check-in whose board name has another form.
199
+ string(LENGTH "${_MIK_NAME}" _MIK_NAME_LENGTH)
200
+ if(_MIK_NAME_LENGTH GREATER 63 OR NOT _MIK_NAME MATCHES
201
+ "^(@[a-z0-9][a-z0-9._-]*/)?[a-z0-9][a-z0-9._-]*(/[a-z0-9]([a-z0-9.-]*[a-z0-9])?)?$")
202
+ message(FATAL_ERROR
203
+ "mikrojs: the board name is \"${_MIK_NAME}\". A board name has at most 63 "
204
+ "characters and the form of a package name, optionally followed by /<board> "
205
+ "(lowercase letters, digits, '.' and '-'). Set MIKROJS_BOARD_NAME.")
206
+ endif()
207
+ target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_BOARD_NAME=\"${_MIK_NAME}\"")
192
208
  endif()
193
209
 
194
210
  # UTC build timestamp. `string(TIMESTAMP ... UTC)` honors SOURCE_DATE_EPOCH
@@ -198,19 +214,21 @@ endif()
198
214
  string(TIMESTAMP _MIK_BUILD_DATE_UTC "%Y-%m-%dT%H:%M:%SZ" UTC)
199
215
  target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_BUILD_DATE_UTC=\"${_MIK_BUILD_DATE_UTC}\"")
200
216
 
201
- # Board name from MIKROJS_BOARD env var (set by CI or developer)
202
- if(DEFINED ENV{MIKROJS_BOARD})
203
- # The registry rejects a check-in whose board name has another shape, and
204
- # the device keeps it in a 48-byte buffer: a bad name means no OTA updates.
205
- string(LENGTH "$ENV{MIKROJS_BOARD}" _MIK_BOARD_NAME_LEN)
206
- if(NOT "$ENV{MIKROJS_BOARD}" MATCHES "^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$" OR _MIK_BOARD_NAME_LEN GREATER 47)
207
- message(FATAL_ERROR
208
- "mikrojs: MIKROJS_BOARD is \"$ENV{MIKROJS_BOARD}\". A board name has at most 47 "
209
- "characters: lowercase letters, digits, '.' and '-', and it starts and ends with a "
210
- "letter or digit.")
211
- endif()
212
- target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_BOARD_NAME=\"$ENV{MIKROJS_BOARD}\"")
217
+ # firmware.json next to flasher_args.json: what the image is, for the tools
218
+ # that read it without a device (mikro flash, mikro fw pack and prepack).
219
+ set(_MIK_FW_JSON "{")
220
+ if(NOT _MIK_NAME STREQUAL "")
221
+ string(APPEND _MIK_FW_JSON "\"name\": \"${_MIK_NAME}\", ")
213
222
  endif()
223
+ if(NOT _MIK_DESCRIPTION STREQUAL "")
224
+ # Free text: escape it for JSON, on one line.
225
+ string(REPLACE "\\" "\\\\" _MIK_DESCRIPTION "${_MIK_DESCRIPTION}")
226
+ string(REPLACE "\"" "\\\"" _MIK_DESCRIPTION "${_MIK_DESCRIPTION}")
227
+ string(REGEX REPLACE "[\r\n\t]+" " " _MIK_DESCRIPTION "${_MIK_DESCRIPTION}")
228
+ string(APPEND _MIK_FW_JSON "\"description\": \"${_MIK_DESCRIPTION}\", ")
229
+ endif()
230
+ string(APPEND _MIK_FW_JSON "\"chip\": \"${IDF_TARGET}\", \"version\": \"${_MIK_VERSION}\"}\n")
231
+ file(WRITE "${CMAKE_BINARY_DIR}/firmware.json" "${_MIK_FW_JSON}")
214
232
 
215
233
  # ── Module lists derived from runtime/modules.json ───────────────────
216
234
  # Feature gating for the derived lists: ble-gated modules compile only when
@@ -227,38 +245,23 @@ else()
227
245
  set(_MIK_WIFI_FLAG "--wifi=off")
228
246
  endif()
229
247
 
230
- # Firmware feature list (e.g. "wifi,i2s,ble"), reported in MSG_READY so the
231
- # host knows which feature-gated builtins this firmware carries.
232
248
  execute_process(
233
- COMMAND node "${MIK_SCRIPTS_DIR}/modules-list.js" features ${_MIK_BLE_FLAG} ${_MIK_WIFI_FLAG}
234
- OUTPUT_VARIABLE _MIK_FEATURES
249
+ COMMAND node "${MIK_SCRIPTS_DIR}/modules-list.js" ${_MIK_BLE_FLAG} ${_MIK_WIFI_FLAG}
250
+ OUTPUT_VARIABLE _MIK_LISTS
235
251
  OUTPUT_STRIP_TRAILING_WHITESPACE
236
- RESULT_VARIABLE _MIK_FEATURES_RESULT
252
+ RESULT_VARIABLE _MIK_LISTS_RESULT
237
253
  )
238
- if(NOT _MIK_FEATURES_RESULT EQUAL 0)
239
- message(FATAL_ERROR "modules-list.js features failed")
254
+ if(NOT _MIK_LISTS_RESULT EQUAL 0)
255
+ message(FATAL_ERROR "modules-list.js failed")
240
256
  endif()
257
+ string(JSON _MIK_FEATURES GET "${_MIK_LISTS}" features)
258
+ string(JSON _MIK_MODULES GET "${_MIK_LISTS}" native)
259
+ string(JSON _MIK_BYTECODE_MODULES GET "${_MIK_LISTS}" bytecode)
260
+
261
+ # Firmware feature list (e.g. "wifi,i2s,ble"), reported in MSG_READY so the
262
+ # host knows which feature-gated builtins this firmware carries.
241
263
  string(REPLACE ";" "," _MIK_FEATURES "${_MIK_FEATURES}")
242
264
  target_compile_definitions(${COMPONENT_LIB} PRIVATE "MIK_FW_FEATURES=\"${_MIK_FEATURES}\"")
243
-
244
- execute_process(
245
- COMMAND node "${MIK_SCRIPTS_DIR}/modules-list.js" native ${_MIK_BLE_FLAG} ${_MIK_WIFI_FLAG}
246
- OUTPUT_VARIABLE _MIK_MODULES
247
- OUTPUT_STRIP_TRAILING_WHITESPACE
248
- RESULT_VARIABLE _MIK_MODULES_RESULT
249
- )
250
- if(NOT _MIK_MODULES_RESULT EQUAL 0)
251
- message(FATAL_ERROR "modules-list.js native failed")
252
- endif()
253
- execute_process(
254
- COMMAND node "${MIK_SCRIPTS_DIR}/modules-list.js" bytecode ${_MIK_BLE_FLAG} ${_MIK_WIFI_FLAG}
255
- OUTPUT_VARIABLE _MIK_BYTECODE_MODULES
256
- OUTPUT_STRIP_TRAILING_WHITESPACE
257
- RESULT_VARIABLE _MIK_BYTECODE_RESULT
258
- )
259
- if(NOT _MIK_BYTECODE_RESULT EQUAL 0)
260
- message(FATAL_ERROR "modules-list.js bytecode failed")
261
- endif()
262
265
  # The lists are read at configure time, so configure must re-run when the
263
266
  # table or the script changes.
264
267
  set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
@@ -273,8 +276,6 @@ mikrojs_force_include_modules(${_MIK_MODULES})
273
276
  mikrojs_generate_bytecode(
274
277
  RUNTIME_DIR "${MIK_RUNTIME_DIR}"
275
278
  MODULES ${_MIK_BYTECODE_MODULES}
276
- MODULE_PREFIX "mikro"
277
- SYMBOL_PREFIX "mikro"
278
279
  TARGET gen_bytecode
279
280
  WORKING_DIRECTORY "${MIKROJS_PROJECT_DIR}"
280
281
  )
@@ -93,7 +93,7 @@ static MIKGpioState* mik__gpio_get(JSContext* ctx, JSValueConst this_val, JSClas
93
93
 
94
94
  /* Claims `gpio` for the handle kind, or returns the GpioInUse Result. */
95
95
  static JSValue mik__gpio_claim(JSContext* ctx, int gpio, MIKGpioKind kind) {
96
- return mik__claim_gpios(ctx, &gpio, 1, s_owner_names[kind]);
96
+ return MIK_ClaimGpios(ctx, &gpio, 1, s_owner_names[kind]);
97
97
  }
98
98
 
99
99
  /* Converts a JS value to a pin level. Returns false unless it is the number 0 or 1. */
@@ -60,7 +60,7 @@ static void mik__i2c_release(MIKI2CState* s) {
60
60
  i2c_del_master_bus(s->bus);
61
61
  s->bus = nullptr;
62
62
  const int gpios[] = {s->sda, s->scl};
63
- mik__release_gpios(gpios, countof(gpios), "I2c");
63
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2c");
64
64
  s->active = false;
65
65
  }
66
66
 
@@ -129,7 +129,7 @@ static JSValue js_i2c(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
129
129
  JSValue invalid = mik__gpio_check(ctx, checks, countof(checks));
130
130
  if (!JS_IsUndefined(invalid)) return invalid;
131
131
  const int gpios[] = {sda, scl};
132
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, countof(gpios), "I2c");
132
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, countof(gpios), "I2c");
133
133
  if (!JS_IsUndefined(claim_failed)) return claim_failed;
134
134
 
135
135
  i2c_master_bus_config_t bus_cfg = {};
@@ -143,7 +143,7 @@ static JSValue js_i2c(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
143
143
  i2c_master_bus_handle_t bus = nullptr;
144
144
  esp_err_t err = i2c_new_master_bus(&bus_cfg, &bus);
145
145
  if (err != ESP_OK) {
146
- mik__release_gpios(gpios, countof(gpios), "I2c");
146
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2c");
147
147
  return mik__result_err_named(ctx, "BusInitFailed", "failed to initialize I2C bus %d: %s",
148
148
  (int)port, esp_err_to_name(err));
149
149
  }
@@ -151,7 +151,7 @@ static JSValue js_i2c(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
151
151
  auto* s = static_cast<MIKI2CState*>(calloc(1, sizeof(MIKI2CState)));
152
152
  if (!s) {
153
153
  i2c_del_master_bus(bus);
154
- mik__release_gpios(gpios, countof(gpios), "I2c");
154
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2c");
155
155
  return JS_ThrowOutOfMemory(ctx);
156
156
  }
157
157
  s->bus = bus;
@@ -193,7 +193,7 @@ static void mik__i2s_finalizer(JSRuntime* rt, JSValue val) {
193
193
  i2s_del_channel(s->rx_chan);
194
194
  }
195
195
  const int gpios[] = MIK__I2S_GPIOS(s);
196
- mik__release_gpios(gpios, countof(gpios), "I2s");
196
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2s");
197
197
  }
198
198
  for (int i = 0; i < s->tx_count; i++) {
199
199
  MIKI2sTxChunk* c = &s->tx_queue[(s->tx_head + i) % MIK_I2S_TX_QUEUE_DEPTH];
@@ -362,7 +362,7 @@ static JSValue js_i2s(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
362
362
  s->din = din;
363
363
 
364
364
  const int gpios[] = MIK__I2S_GPIOS(s);
365
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, countof(gpios), "I2s");
365
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, countof(gpios), "I2s");
366
366
  if (!JS_IsUndefined(claim_failed)) {
367
367
  free(s);
368
368
  return claim_failed;
@@ -374,7 +374,7 @@ static JSValue js_i2s(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
374
374
  if (err == ESP_OK && !mik__i2s_track(ctx, s)) err = ESP_ERR_NO_MEM;
375
375
  if (err != ESP_OK) {
376
376
  mik__i2s_del_channels(s);
377
- mik__release_gpios(gpios, countof(gpios), "I2s");
377
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2s");
378
378
  free(s);
379
379
  return mik__result_err_named(ctx, "ChannelInitFailed", "I2S init failed: %s",
380
380
  esp_err_to_name(err));
@@ -385,7 +385,7 @@ static JSValue js_i2s(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
385
385
  if (JS_IsException(obj)) {
386
386
  mik__i2s_untrack(ctx, s);
387
387
  mik__i2s_del_channels(s);
388
- mik__release_gpios(gpios, countof(gpios), "I2s");
388
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2s");
389
389
  free(s);
390
390
  return obj;
391
391
  }
@@ -421,7 +421,7 @@ static JSValue js_i2s_end(JSContext* ctx, JSValue this_val, int argc, JSValue* a
421
421
  mik__i2s_untrack(ctx, s);
422
422
  mik__i2s_del_channels(s);
423
423
  const int gpios[] = MIK__I2S_GPIOS(s);
424
- mik__release_gpios(gpios, countof(gpios), "I2s");
424
+ MIK_ReleaseGpios(gpios, countof(gpios), "I2s");
425
425
  s->active = false;
426
426
  MIK_DropHandle(ctx, this_val);
427
427
  return JS_UNDEFINED;
@@ -565,8 +565,7 @@ void MIK_Main(void) {
565
565
  * (USB-Serial/JTAG). `printf` goes to newlib stdio, which
566
566
  * ESP-IDF routes to UART0 and is invisible over USB. */
567
567
  const char* core_word = chip_info.cores == 1 ? "core" : "cores";
568
- const MIKPlatform* plat = MIK_GetPlatform();
569
- const char* dev_id = plat->get_device_id ? plat->get_device_id() : nullptr;
568
+ const char* dev_id = MIK_GetPlatform()->get_device_id();
570
569
  char banner[160];
571
570
  int off = 0;
572
571
  #ifdef MIK_FW_VERSION
@@ -577,9 +576,7 @@ void MIK_Main(void) {
577
576
  JS_GetVersion(), CONFIG_IDF_TARGET);
578
577
  #endif
579
578
  /* Stable per-board device id (chip MAC as Crockford Base32). */
580
- if (dev_id && dev_id[0]) {
581
- off += snprintf(banner + off, sizeof(banner) - off, " (%s)", dev_id);
582
- }
579
+ off += snprintf(banner + off, sizeof(banner) - off, " (%s)", dev_id);
583
580
  off += snprintf(banner + off, sizeof(banner) - off, ", %d %s", chip_info.cores, core_word);
584
581
  if (has_wifi || has_bt || has_ble) {
585
582
  off += snprintf(banner + off, sizeof(banner) - off, ", ");
@@ -213,7 +213,7 @@ static JSValue js_neopixel(JSContext* ctx, JSValue this_val, int argc, JSValue*
213
213
  JSValue invalid = mik__gpio_check(ctx, &check, 1);
214
214
  if (!JS_IsUndefined(invalid)) return invalid;
215
215
  const int gpios[] = {gpio};
216
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, 1, "NeoPixel");
216
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, 1, "NeoPixel");
217
217
  if (!JS_IsUndefined(claim_failed)) return claim_failed;
218
218
 
219
219
  int bytes_per_led = rgbw ? 4 : 3;
@@ -186,14 +186,14 @@ static JSValue mik__nvs_kv_remove(JSContext* ctx, JSValue this_val, int argc, JS
186
186
  esp_err_t err = nvs_erase_key(handle, key);
187
187
  JS_FreeCString(ctx, key);
188
188
 
189
- if (err != ESP_OK) {
189
+ /* A missing key is already removed, not a failure. */
190
+ if (err == ESP_ERR_NVS_NOT_FOUND) {
190
191
  nvs_close(handle);
191
- return JS_NewBool(ctx, false);
192
+ return JS_NewBool(ctx, true);
192
193
  }
193
-
194
- nvs_commit(handle);
194
+ if (err == ESP_OK) err = nvs_commit(handle);
195
195
  nvs_close(handle);
196
- return JS_NewBool(ctx, true);
196
+ return JS_NewBool(ctx, err == ESP_OK);
197
197
  }
198
198
 
199
199
  static JSValue mik__nvs_kv_clear(JSContext* ctx, JSValue this_val, int argc, JSValue* argv,
@@ -339,9 +339,8 @@ bool env_read_app_version(void* opaque, char* out, size_t out_len) {
339
339
  const MIKOtaEnv* mik__ota_env_for(MIKRuntime* rt, int bytecode_version) {
340
340
  g_state.rt = rt;
341
341
  g_state.bytecode_version = bytecode_version;
342
- const MIKPlatform* platform = MIK_GetPlatform();
343
- const char* id = platform->get_device_id ? platform->get_device_id() : nullptr;
344
- snprintf(g_state.device_id, sizeof(g_state.device_id), "%s", id ? id : "");
342
+ snprintf(g_state.device_id, sizeof(g_state.device_id), "%s",
343
+ MIK_GetPlatform()->get_device_id());
345
344
 
346
345
  MIKOtaEnv& env = g_state.env;
347
346
  env = {};
@@ -232,7 +232,7 @@ static JSValue js_pwm(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
232
232
  invalid = mik__gpio_check(ctx, &check, 1);
233
233
  if (!JS_IsUndefined(invalid)) return invalid;
234
234
  const int gpios[] = {gpio};
235
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, 1, "Pwm");
235
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, 1, "Pwm");
236
236
  if (!JS_IsUndefined(claim_failed)) return claim_failed;
237
237
 
238
238
  int ch = mik__pwm_alloc_channel();
@@ -157,26 +157,29 @@ static JSValue mik__rtc_set(JSContext* ctx, JSValue this_val, int argc, JSValue*
157
157
  auto* hdr = reinterpret_cast<RtcHeader*>(s_rtc_store);
158
158
  uint8_t* data = mik__rtc_data();
159
159
 
160
- /* Remove existing entry if present */
160
+ /* Check space before changing anything, counting the bytes an existing entry would free, so
161
+ * a failed set keeps the old value and a valid CRC */
161
162
  uint8_t* next = nullptr;
162
163
  uint8_t* found = mik__rtc_find(key, static_cast<uint8_t>(key_len), &next);
164
+ size_t old_size = (found && next) ? static_cast<size_t>(next - found) : 0;
165
+ size_t used = hdr->data_len - old_size;
166
+ size_t needed = mik__rtc_entry_size(static_cast<uint8_t>(key_len), static_cast<uint16_t>(val_len));
167
+ if (used + needed > MIK_RTC_DATA_CAPACITY) {
168
+ JS_FreeCString(ctx, key);
169
+ return mik__result_err(ctx, MIK_ERR_KV_STORAGE_FULL, 0,
170
+ "RTC memory full (need %zu bytes, %zu available)", needed,
171
+ MIK_RTC_DATA_CAPACITY - used);
172
+ }
173
+
174
+ /* Remove existing entry if present */
163
175
  if (found && next) {
164
176
  uint8_t* end = data + hdr->data_len;
165
177
  size_t tail = end - next;
166
178
  memmove(found, next, tail);
167
- hdr->data_len -= static_cast<uint16_t>(next - found);
179
+ hdr->data_len -= static_cast<uint16_t>(old_size);
168
180
  hdr->entry_count--;
169
181
  }
170
182
 
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
183
  /* Append new entry: encode CBOR directly into the RTC store */
181
184
  uint8_t* write_pos = data + hdr->data_len;
182
185
  write_pos[0] = static_cast<uint8_t>(key_len);
@@ -250,7 +253,8 @@ static JSValue mik__rtc_remove(JSContext* ctx, JSValue this_val, int argc, JSVal
250
253
  uint8_t* found = mik__rtc_find(key, static_cast<uint8_t>(key_len), &next);
251
254
  JS_FreeCString(ctx, key);
252
255
 
253
- if (!found || !next) return JS_NewBool(ctx, false);
256
+ /* A missing key is already removed, not a failure. */
257
+ if (!found || !next) return JS_NewBool(ctx, true);
254
258
 
255
259
  uint8_t* end = data + hdr->data_len;
256
260
  size_t tail = end - next;
@@ -34,7 +34,7 @@ static void mik__spi_release(MIKSPIState* s) {
34
34
  s->device = nullptr;
35
35
  spi_bus_free(s->host);
36
36
  const int gpios[] = {s->clk, s->mosi, s->miso, s->cs};
37
- mik__release_gpios(gpios, countof(gpios), "Spi");
37
+ MIK_ReleaseGpios(gpios, countof(gpios), "Spi");
38
38
  s->active = false;
39
39
  }
40
40
 
@@ -95,7 +95,7 @@ static JSValue js_spi(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
95
95
  JSValue invalid = mik__gpio_check(ctx, checks, countof(checks));
96
96
  if (!JS_IsUndefined(invalid)) return invalid;
97
97
  const int gpios[] = {clk, mosi, miso, cs};
98
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, countof(gpios), "Spi");
98
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, countof(gpios), "Spi");
99
99
  if (!JS_IsUndefined(claim_failed)) return claim_failed;
100
100
 
101
101
  /* SPI2_HOST is 1, SPI3_HOST is 2, etc. — host maps directly */
@@ -110,7 +110,7 @@ static JSValue js_spi(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
110
110
 
111
111
  esp_err_t err = spi_bus_initialize(host_id, &bus_cfg, SPI_DMA_CH_AUTO);
112
112
  if (err != ESP_OK) {
113
- mik__release_gpios(gpios, countof(gpios), "Spi");
113
+ MIK_ReleaseGpios(gpios, countof(gpios), "Spi");
114
114
  return mik__result_err_named(ctx, "BusInitFailed", "SPI bus init failed: %s",
115
115
  esp_err_to_name(err));
116
116
  }
@@ -129,7 +129,7 @@ static JSValue js_spi(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
129
129
  err = spi_bus_add_device(host_id, &dev_cfg, &device);
130
130
  if (err != ESP_OK) {
131
131
  spi_bus_free(host_id);
132
- mik__release_gpios(gpios, countof(gpios), "Spi");
132
+ MIK_ReleaseGpios(gpios, countof(gpios), "Spi");
133
133
  return mik__result_err_named(ctx, "AddDeviceFailed", "failed to add SPI device: %s",
134
134
  esp_err_to_name(err));
135
135
  }
@@ -138,7 +138,7 @@ static JSValue js_spi(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
138
138
  if (!s) {
139
139
  spi_bus_remove_device(device);
140
140
  spi_bus_free(host_id);
141
- mik__release_gpios(gpios, countof(gpios), "Spi");
141
+ MIK_ReleaseGpios(gpios, countof(gpios), "Spi");
142
142
  return JS_ThrowOutOfMemory(ctx);
143
143
  }
144
144
  s->device = device;
@@ -74,7 +74,7 @@ static void mik__uart_release(MIKRuntime* mik_rt, MIKUartState* s) {
74
74
  mik__uart_untrack(mik_rt, s);
75
75
  uart_driver_delete(s->port);
76
76
  const int gpios[] = {s->tx_pin, s->rx_pin};
77
- mik__release_gpios(gpios, countof(gpios), "Uart");
77
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
78
78
  s->active = false;
79
79
  }
80
80
 
@@ -130,7 +130,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
130
130
  JSValue invalid = mik__gpio_check(ctx, checks, countof(checks));
131
131
  if (!JS_IsUndefined(invalid)) return invalid;
132
132
  const int gpios[] = {tx, rx};
133
- JSValue claim_failed = mik__claim_gpios(ctx, gpios, countof(gpios), "Uart");
133
+ JSValue claim_failed = MIK_ClaimGpios(ctx, gpios, countof(gpios), "Uart");
134
134
  if (!JS_IsUndefined(claim_failed)) return claim_failed;
135
135
 
136
136
  auto uart_port = static_cast<uart_port_t>(port);
@@ -138,7 +138,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
138
138
  * refused before uart_param_config and uart_set_pin can change its baud
139
139
  * rate and pins. */
140
140
  if (uart_is_driver_installed(uart_port)) {
141
- mik__release_gpios(gpios, countof(gpios), "Uart");
141
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
142
142
  return mik__result_err_named(ctx, "DriverInstallFailed", "port %d is already in use",
143
143
  (int)port);
144
144
  }
@@ -153,7 +153,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
153
153
 
154
154
  esp_err_t err = uart_param_config(uart_port, &uart_config);
155
155
  if (err != ESP_OK) {
156
- mik__release_gpios(gpios, countof(gpios), "Uart");
156
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
157
157
  return mik__result_err_named(ctx, "InvalidParam",
158
158
  "uart_param_config failed on port %d: %s", (int)port,
159
159
  esp_err_to_name(err));
@@ -162,7 +162,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
162
162
  err = uart_set_pin(uart_port, tx >= 0 ? tx : UART_PIN_NO_CHANGE,
163
163
  rx >= 0 ? rx : UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
164
164
  if (err != ESP_OK) {
165
- mik__release_gpios(gpios, countof(gpios), "Uart");
165
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
166
166
  return mik__result_err_named(ctx, "SetPinFailed",
167
167
  "uart_set_pin failed on port %d (tx=%d, rx=%d): %s", (int)port,
168
168
  tx, rx, esp_err_to_name(err));
@@ -173,7 +173,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
173
173
  int rx_buf = rx >= 0 ? MIK_UART_RX_BUF_SIZE : UART_HW_FIFO_LEN(uart_port) + 1;
174
174
  err = uart_driver_install(uart_port, rx_buf, 0, 0, nullptr, ESP_INTR_FLAG_IRAM);
175
175
  if (err != ESP_OK) {
176
- mik__release_gpios(gpios, countof(gpios), "Uart");
176
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
177
177
  return mik__result_err_named(ctx, "DriverInstallFailed",
178
178
  "uart_driver_install failed on port %d: %s", (int)port,
179
179
  esp_err_to_name(err));
@@ -182,7 +182,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
182
182
  auto* s = static_cast<MIKUartState*>(calloc(1, sizeof(MIKUartState)));
183
183
  if (!s) {
184
184
  uart_driver_delete(uart_port);
185
- mik__release_gpios(gpios, countof(gpios), "Uart");
185
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
186
186
  return JS_ThrowOutOfMemory(ctx);
187
187
  }
188
188
  s->port = uart_port;
@@ -194,7 +194,7 @@ static JSValue js_uart(JSContext* ctx, JSValue this_val, int argc, JSValue* argv
194
194
  JSValue obj = JS_NewObjectClass(ctx, mik_uart_class_id);
195
195
  if (JS_IsException(obj)) {
196
196
  uart_driver_delete(uart_port);
197
- mik__release_gpios(gpios, countof(gpios), "Uart");
197
+ MIK_ReleaseGpios(gpios, countof(gpios), "Uart");
198
198
  free(s);
199
199
  return obj;
200
200
  }
@@ -300,9 +300,7 @@ static bool mik__wifi_apply_hostname(JSContext* ctx) {
300
300
  hostname = buf;
301
301
  }
302
302
  if (!hostname) {
303
- const char* dev_id = MIK_GetPlatform()->get_device_id();
304
- if (!dev_id || !dev_id[0]) return false;
305
- snprintf(buf, sizeof(buf), "mikrojs-%s", dev_id);
303
+ snprintf(buf, sizeof(buf), "mikrojs-%s", MIK_GetPlatform()->get_device_id());
306
304
  hostname = buf;
307
305
  }
308
306
  return esp_netif_set_hostname(s_sta_netif, hostname) == ESP_OK;