App kit guide
The guide that comes with the app kit of Reload OS 1.4.0 (README.md in the kit), as it is in the kit.
Build your own apps for a Reload OS device - the 7" ESP32-P4 touch kiosk by Reloader Co.,Ltd. - without the firmware's source. An app is a small ELF file that you copy into the apps/ folder of the device's flash drive or SD card; the launcher shows it as a tile, and the device loads it when the tile is tapped. hello/ is a complete example. The kit matches the firmware version in reloados/VERSION.
appkit/
README.md this file
build.sh ./build.sh <app> [jc|jc4880|jc8012|matouch] builds one app for one board
new.sh ./new.sh <name> starts a new app from the Hello example
hello/ the example: CMakeLists.txt, main/hello.c, main/CMakeLists.txt, main/idf_component.yml
reloados/ from the firmware - do not edit:
VERSION which firmware this kit belongs to
api.txt every function and variable an app may call
main/ros.h the Reload OS API (and ros_board.h: the boards)
sdkconfig.* the firmware's configuration, which the app is built against
elfapp.cmake the build rules; components/: the firmware's menu, needed by ros_board.h
#Requirements
- ESP-IDF v5.5.4 - exactly this version, the one the firmware is built with (an app is linked against the firmware on the device; LVGL's and FreeRTOS's structures must be laid out the same way). Install it as Espressif describes (https://docs.espressif.com/projects/esp-idf/en/v5.5.4/esp32p4/get-started/), with the ESP32-P4 target, and run its
export.shin the terminal you build from.build.shfinds an installation in~/.espressif/v5.5.4/esp-idfor~/esp/esp-idfby itself. - Internet for the first build: the component manager downloads LVGL 9.2.2, the ELF loader 1.3.3 and cmake_utilities 0.5.3 (the versions in
main/idf_component.yml; don't change them). - macOS or Linux (bash). Keep the kit outside folders that a cloud drive syncs (iCloud Drive, OneDrive, Dropbox): they evict and re-download the thousands of files a build makes.
#Build the example
./build.sh hello # -> hello/jc1060p470c/hello.elf (the JC1060P470C, the production board)
./build.sh hello matouch # -> hello/matouch-p4-7/hello.elf (the Makerfabs MaTouch development board)
./build.sh hello jc4880 # -> hello/jc4880p443/hello.elf (the Guition JC4880P443, 4.3" 480x800)
./build.sh hello jc8012 # -> hello/jc8012p4a1/hello.elf (the Guition JC8012P4A1, 10.1" 800x1280)
An app is built for one board (it gets that board's configuration and pins); the device refuses the other board's file with a message naming both. The script ends with a check that everything the app calls is in the firmware's table - see "What an app can call".
#In VS Code
Open the appkit folder (File › Open Folder). Terminal › Run Build Task (⇧⌘B) builds the app whose file is open in the editor (hello when none is) with ./build.sh, for the JC1060P470C; the other tasks build for the MaTouch and start a new app. Build in the integrated terminal with ./build.sh hello works just the same.
VS Code opens a new folder in Restricted Mode, where tasks don't run: trust the folder (the banner's Manage › Trust). The ESP-IDF extension picks the newest ESP-IDF installed and the esp32 target for a folder it has not seen: set them to v5.5.4 and esp32p4 in the status bar. (build.sh looks for v5.5.4 itself when the terminal holds another version, and stops when there is none.)
Don't use the ESP-IDF extension's Build / Flash / Monitor buttons on this folder: they build and link a whole firmware, which an app is not (its functions are the firmware's, found when it is loaded), so that build fails - and an app is never flashed, it is copied to the device. The extension may still write its .vscode/settings.json and .clangd here; they do no harm. For IntelliSense after the first build, point clangd or the C/C++ extension at <app>/build_jc/compile_commands.json.
#Put it on the device
Copy the .elf into the apps/ folder (make it if it is not there) of
- the device's flash drive - the usual place. From a browser: the remote desktop's Files page,
http://<device>/files(Settings › Remote desktop on the device gives the address and the login) - Flash › apps › Upload. Or with the device plugged into a PC as a USB drive (Settings › USB › USB_OTG port: Drive for PC), or from a card with the device's Files app (long-press the file › Copy › Flash). - or the SD card (
apps/on the card, in a card reader).
Then restart the device: the launcher reads the folders at start. The flash drive is read first, the card second; a file both have opens from the flash drive (the card's copy opens only when the flash drive's can't be used). To update an app that is on the flash drive, replace it there - a newer file on the card alone does not run.
The tile shows the app's name, icon and colour from its ROS_ELFAPP_META(...) line (read from the file without loading it). Tap it: the file is read into PSRAM, checked, linked against the firmware and run. Back (swipe from the left edge) or the home button closes it; when nothing of it runs any more it is unloaded.
#Your own app
./new.sh myapp # -> myapp/ : a copy of hello/ under the new name
# edit myapp/main/myapp.c
./build.sh myapp # -> myapp/jc1060p470c/myapp.elf
The shape of an app (hello/main/hello.c):
#include "ros.h"
static void create(lv_obj_t *body) /* build the screen inside body; runs on the LVGL task */
{
ros_ui_label(body, &lv_font_montserrat_32, ROS_C_TEXT, "Hello");
}
static void destroy(void) /* the app closes: delete timers, stop what you started */
{
}
ROS_ELFAPP_META("Hello", LV_SYMBOL_SD_CARD, 0x6B4FBB); /* the tile: name, glyph, colour */
static const ros_elfapp_t s_app = {
.abi = ROS_ELFAPP_ABI, .board = ROS_BOARD_SHORT, .meta = &ros_elfapp_meta,
.app = { .name = "Hello", .icon = LV_SYMBOL_SD_CARD, .color = 0x6B4FBB, .create = create, .destroy = destroy },
};
int main(int argc, char *argv[]) /* only hands the app back; the work starts in create() */
{
if (argc > 0 && argv[0]) *(const ros_elfapp_t **)argv[0] = &s_app;
return 0;
}
bodyis an empty full-screen LVGL object under the status bar; everything you create inside it is deleted by the system when the app closes. The screen is 1024×600 or 600×1024: the device may be mounted horizontally or vertically. UseROS_SCR_W,ROS_SCR_HandROS_PORTRAIT(values, not constants) and check both ways.ros.his the API:ros_ui_label(),ros_ui_button(),ros_toast(), the coloursROS_C_*(they follow the device's light / dark theme),ros_lock()/ros_unlock()for LVGL calls from your own tasks,ros_audio_*,ros_settings(), the network, SD card, camera, USB, printer and card reader services. Every LVGL 9.2.2 function the firmware itself uses is available too (api.txt), and the fontslv_font_montserrat_14...48and the Thai fontsrld_font_thai_*.- The icon is an
LV_SYMBOL_*glyph, or"@name"for one of the firmware's drawn icons. A picture instead:ROS_ELFAPP_META_FLAGS(..., ROS_ELFAPP_ON_WHITE)andROS_ELFAPP_TILE("path/to/icon.png")(PNG, at most 64 KB; the path is best given frommain/CMakeLists.txtwithtarget_compile_definitions). Files an app needs (pictures, sounds) go into the app withEMBED_FILESinmain/CMakeLists.txt, as any ESP-IDF project embeds them. - Headers of ESP-IDF parts you use (
nvs_flash,esp_driver_uart, ...) are added toREQUIRESinmain/CMakeLists.txt- for the headers only; the functions are the firmware's, if it exports them.
#What an app can call
Only what the firmware exports: the names in reloados/api.txt (the Reload OS API from ros.h, the LVGL functions the firmware uses, the fonts, the C library, FreeRTOS and ESP-IDF basics). build.sh compares your app with the list and refuses to call it usable when a name is missing - on the device such an app is refused with "Needs a function this firmware doesn't have", and the device's log names it. If you need something that is not in the list, tell Reloader: the next firmware can export it (a name costs 8 bytes), and your app then needs that firmware or newer.
The interface number ROS_ELFAPP_ABI (in ros.h) is checked when the app is opened: a kit of a later firmware may carry a new number, and apps are then rebuilt with it ("Made for app interface N, this firmware has M" on the screen). Apps built with this kit run on this firmware version and on later ones with the same number.
#Rules - the ones that restart the device when broken
An app runs with the firmware's full rights, in the firmware's memory: a crash in it restarts the device, and so does blocking the screen.
- Nothing that waits on the LVGL task.
create(),destroy(), LVGL event and timer callbacks run on the LVGL task: no network, file, serial or card-reader work there - start a task for it and send the result back (ros_lock(ms)/ros_unlock()around LVGL calls from the task, and check the app is still open). A screen that does not respond for 60 s restarts the device. - Count your tasks. The app's code is freed when it is unloaded, so nothing may still run it: every task of your own adds 1 to a counter before it is created (
__atomic_add_fetch(&s_live, 1, __ATOMIC_SEQ_CST)), the app gives.tasks = &s_live, and each task ends withros_task_end(&s_live, false)- never withvTaskDelete(). The firmware unloads the app only when the counter is back to 0 (a task still finishing a network call simply ends first). - Clean up in
destroy(): LVGL timers, anything you put onlv_layer_top()(keyboards, message boxes), display event callbacks, sounds you started. - Every load starts with fresh variables. What the app makes once and keeps across opens (a queue, a mutex, a buffer) is made again at every load: give
.unload = fnand free it there, or it leaks. - Big buffers come from PSRAM:
heap_caps_malloc(size, MALLOC_CAP_SPIRAM). Internal RAM is the scarce one (about 100 KB free), andmalloc()of less than 4 KB takes it. - A stream buffer you delete gets its own memory (
xStreamBufferCreateStatic()on a PSRAM block, thenvStreamBufferDelete()+free()): ESP-IDF 5.5.4'svStreamBufferDeleteWithCaps()frees it twice. - Don't edit
reloados/:ros.hdescribes the firmware's structures as they are - a changed header builds an app that reads the wrong bytes.
#When something is wrong
| On the device | Why |
|---|---|
| no tile for the file | not in apps/ of the flash drive or the card, or the device was not restarted; a file name longer than about 65 characters is left out (the log says) |
| "Built for the X, this is a Y" | built for the other board: ./build.sh <app> jc for the JC1060P470C |
| "Made for app interface N, this firmware has M" | the kit and the firmware don't match: use the kit of that firmware |
| "Needs a function this firmware doesn't have" | a name not in api.txt (build.sh would have said) - or a kit newer than the firmware |
| "Damaged or not a Reload OS app" | the copy is incomplete - copy the file again |
| the device restarts when the app runs | a crash in the app; the device's log (USB serial, 115200) and Settings › About show the crash record |
Questions and names to export: Reloader Co.,Ltd.