diff --git a/.github/actions/build-app/action.yml b/.github/actions/build-app/action.yml index 4cdb45e..4d9d1b7 100644 --- a/.github/actions/build-app/action.yml +++ b/.github/actions/build-app/action.yml @@ -23,6 +23,8 @@ runs: uses: actions/upload-artifact@v4 with: name: ${{ inputs.app_name }} - path: Apps/${{ inputs.app_name}}/build/${{ inputs.app_name }}.app + # The built package is named after the app's manifest app.id (e.g. tactility.helloworld), + # not the app directory name - glob instead of assuming they match. + path: Apps/${{ inputs.app_name }}/build/*.app retention-days: 30 diff --git a/Apps/Brainfuck/CMakeLists.txt b/Apps/Brainfuck/CMakeLists.txt index 79c2d14..606d3fb 100644 --- a/Apps/Brainfuck/CMakeLists.txt +++ b/Apps/Brainfuck/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Brainfuck) -tactility_project(Brainfuck) +tactility_project_pre(tactility.brainfuck) +project(tactility.brainfuck) +tactility_project_post(tactility.brainfuck) diff --git a/Apps/Brainfuck/main/CMakeLists.txt b/Apps/Brainfuck/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/Brainfuck/main/CMakeLists.txt +++ b/Apps/Brainfuck/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/Brainfuck/manifest.properties b/Apps/Brainfuck/manifest.properties index 50d4fe3..c42c8ad 100644 --- a/Apps/Brainfuck/manifest.properties +++ b/Apps/Brainfuck/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.brainfuck -app.version.name=0.10.0 -app.version.code=10 +app.version.name=0.11.0 +app.version.code=11 app.name=Brainfuck interpreter app.description=Brainfuck esoteric language interpreter diff --git a/Apps/Breakout/CMakeLists.txt b/Apps/Breakout/CMakeLists.txt index f6dfb19..a5e23d7 100644 --- a/Apps/Breakout/CMakeLists.txt +++ b/Apps/Breakout/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Breakout) -tactility_project(Breakout) +tactility_project_pre(tactility.breakout) +project(tactility.breakout) +tactility_project_post(tactility.breakout) diff --git a/Apps/Breakout/main/CMakeLists.txt b/Apps/Breakout/main/CMakeLists.txt index 531d730..e7f8231 100644 --- a/Apps/Breakout/main/CMakeLists.txt +++ b/Apps/Breakout/main/CMakeLists.txt @@ -1,10 +1,6 @@ +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") + file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SFX_ENGINE_FILES ../../../Libraries/SfxEngine/Source/*.c*) -idf_component_register( - SRCS ${SOURCE_FILES} ${SFX_ENGINE_FILES} - # Library headers must be included directly, - # because all regular dependencies get stripped by elf_loader's cmake script - INCLUDE_DIRS ../../../Libraries/SfxEngine/Include - REQUIRES TactilitySDK -) +tactility_component_register(SRCS ${SOURCE_FILES} ${SFX_ENGINE_FILES} INCLUDE_DIRS ../../../Libraries/SfxEngine/Include) diff --git a/Apps/Breakout/manifest.properties b/Apps/Breakout/manifest.properties index 0504db7..1595a9d 100644 --- a/Apps/Breakout/manifest.properties +++ b/Apps/Breakout/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.breakout -app.version.name=0.13.0 -app.version.code=13 +app.version.name=0.14.0 +app.version.code=14 app.name=Breakout app.description=Classic brick-breaking arcade game diff --git a/Apps/Calculator/CMakeLists.txt b/Apps/Calculator/CMakeLists.txt index 1fc8491..e6cd874 100644 --- a/Apps/Calculator/CMakeLists.txt +++ b/Apps/Calculator/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Calculator) -tactility_project(Calculator) +tactility_project_pre(tactility.calculator) +project(tactility.calculator) +tactility_project_post(tactility.calculator) diff --git a/Apps/Calculator/main/CMakeLists.txt b/Apps/Calculator/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/Calculator/main/CMakeLists.txt +++ b/Apps/Calculator/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/Calculator/manifest.properties b/Apps/Calculator/manifest.properties index fd99629..0898279 100644 --- a/Apps/Calculator/manifest.properties +++ b/Apps/Calculator/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.calculator -app.version.name=0.11.0 -app.version.code=11 +app.version.name=0.12.0 +app.version.code=12 app.name=Calculator diff --git a/Apps/Diceware/CMakeLists.txt b/Apps/Diceware/CMakeLists.txt index 5ddb44f..56515ee 100644 --- a/Apps/Diceware/CMakeLists.txt +++ b/Apps/Diceware/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Diceware) -tactility_project(Diceware) +tactility_project_pre(tactility.diceware) +project(tactility.diceware) +tactility_project_post(tactility.diceware) diff --git a/Apps/Diceware/main/CMakeLists.txt b/Apps/Diceware/main/CMakeLists.txt index 94136e8..2c68aad 100644 --- a/Apps/Diceware/main/CMakeLists.txt +++ b/Apps/Diceware/main/CMakeLists.txt @@ -1,9 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) - -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/Diceware/manifest.properties b/Apps/Diceware/manifest.properties index 50dc940..a379ff3 100644 --- a/Apps/Diceware/manifest.properties +++ b/Apps/Diceware/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.diceware -app.version.name=0.13.0 -app.version.code=13 +app.version.name=0.14.0 +app.version.code=14 app.name=Diceware diff --git a/Apps/EpubReader/CMakeLists.txt b/Apps/EpubReader/CMakeLists.txt index c0da026..31c6298 100644 --- a/Apps/EpubReader/CMakeLists.txt +++ b/Apps/EpubReader/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(EpubReader) -tactility_project(EpubReader) +tactility_project_pre(tactility.epubreader) +project(tactility.epubreader) +tactility_project_post(tactility.epubreader) diff --git a/Apps/EpubReader/main/CMakeLists.txt b/Apps/EpubReader/main/CMakeLists.txt index fcf9f4b..22d715c 100644 --- a/Apps/EpubReader/main/CMakeLists.txt +++ b/Apps/EpubReader/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK esp_rom -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} REQUIRES esp_rom) diff --git a/Apps/EpubReader/manifest.properties b/Apps/EpubReader/manifest.properties index c57d841..cf84f8c 100644 --- a/Apps/EpubReader/manifest.properties +++ b/Apps/EpubReader/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 app.id=tactility.epubreader -app.version.name=0.10.0 -app.version.code=10 +app.version.name=0.11.0 +app.version.code=11 app.name=Epub Reader app.description=Epub and text file reader. Requires PSRAM! diff --git a/Apps/EspNowBridge/CMakeLists.txt b/Apps/EspNowBridge/CMakeLists.txt index 1e89b68..528a4cb 100644 --- a/Apps/EspNowBridge/CMakeLists.txt +++ b/Apps/EspNowBridge/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(EspNowBridge) -tactility_project(EspNowBridge) +tactility_project_pre(tactility.espnowbridge) +project(tactility.espnowbridge) +tactility_project_post(tactility.espnowbridge) diff --git a/Apps/EspNowBridge/main/CMakeLists.txt b/Apps/EspNowBridge/main/CMakeLists.txt index 025768b..8ad0519 100644 --- a/Apps/EspNowBridge/main/CMakeLists.txt +++ b/Apps/EspNowBridge/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK bootloader_support esp_app_format -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} REQUIRES bootloader_support esp_app_format) diff --git a/Apps/EspNowBridge/main/Source/EspNowBridge.cpp b/Apps/EspNowBridge/main/Source/EspNowBridge.cpp index 76eca69..bfb6345 100644 --- a/Apps/EspNowBridge/main/Source/EspNowBridge.cpp +++ b/Apps/EspNowBridge/main/Source/EspNowBridge.cpp @@ -1,12 +1,13 @@ #include "EspNowBridge.h" +#include #include +#include #include #include #include #include -#include #include #include @@ -22,9 +23,11 @@ constexpr TickType_t LVGL_DEFAULT_LOCK_TIME = 500; // 500 ticks = 500 ms #include #include +#include #include #include #include +#include static constexpr auto* TAG = "EspNowBridge"; static constexpr size_t CHUNK_SIZE = 1500; @@ -344,6 +347,7 @@ static void performUpdate(Context* ctx, const std::string& filePath) { FILE* file = fopen(filePath.c_str(), "rb"); if (file == nullptr) { + LOG_E(TAG, "Failed to open '%s' (len=%zu): %s", filePath.c_str(), filePath.size(), strerror(errno)); dispatchToUi(ctx, [](Context& app, void*) { setStatus(&app, "Failed to open selected file"); setUpdateButtonsDisabled(&app, false); @@ -546,12 +550,37 @@ static void startUpdateTask(Context* ctx, const std::string& filePath) { } } +// Matches Tactility's own built-in file-selection system app (Tactility/Source/app/fileselection/ +// FileSelection.cpp) - its manifest id and argv convention aren't part of any public app-module +// header (that app isn't generic app-module framework, just one particular app shipped by +// Tactility), so external apps reach it by calling app_manager_start_for_result_with_streams() +// against these directly, the same way Tactility's own built-in apps (e.g. Notes) do internally. +static constexpr auto* FILE_SELECTION_APP_ID = "tactility.fileselection"; +static constexpr auto* FILE_SELECTION_MODE_EXISTING = "--existing"; + static void onUpdateButtonClicked(lv_event_t* /*event*/) { auto* ctx = liveInstance.load(); if (ctx == nullptr || !isWifiRadioOn(ctx)) { return; } - ctx->pickFileLaunchId = tt_app_fileselection_start_for_existing_file(ctx->appInstanceId); + if (ctx->pickFileLaunchId != 0) { + // A second tap (e.g. before the dialog has visibly opened) would overwrite + // pickFileLaunchId with the new instance's id, so the first dialog's eventual + // APP_EVENT_RESULT would never match it and its picked path would be silently dropped. + return; + } + const char* argv[] = { FILE_SELECTION_MODE_EXISTING }; + AppStreamBinding binding = { + .producer_fd = STDOUT_FILENO, + .stream = &ctx->pickFileStream, + .buffer = ctx->pickFileBuffer, + .buffer_capacity = sizeof(ctx->pickFileBuffer), + .event_group = ctx->eventGroup, + }; + uint32_t instanceId = 0; + if (app_manager_start_for_result_with_streams(FILE_SELECTION_APP_ID, ctx->appInstanceId, 1, argv, &binding, 1, &instanceId) == ERROR_NONE) { + ctx->pickFileLaunchId = instanceId; + } } // Name of the slave bridge firmware bundled in this app's assets/ folder @@ -640,6 +669,7 @@ static void onEnableWifiButtonClicked(lv_event_t* /*event*/) { void espNowBridgeInit(Context* ctx, TaskEventGroup* eventGroup) { ctx->taskDoneSemaphore = xSemaphoreCreateBinary(); + ctx->eventGroup = eventGroup; liveInstance = ctx; Device* wifiDevice = nullptr; @@ -718,6 +748,14 @@ void espNowBridgeCreateWidgets(lv_obj_t* parent, void* userData) { espNowBridgeApplyPendingUpdate(ctx); } +void espNowBridgeDestroyWidgets(void* userData) { + auto* ctx = static_cast(userData); + // Per WindowDestroyWidgetsFn's contract: only touch memory that needs no other + // synchronization. isShown is exactly that - dispatchToUi() only ever reads it under this + // same flag, never a lock this callback could deadlock against. + ctx->isShown = false; +} + void espNowBridgeApplyPendingUpdate(Context* ctx) { if (ctx->pendingUpdateFilePath.empty()) { return; diff --git a/Apps/EspNowBridge/main/Source/EspNowBridge.h b/Apps/EspNowBridge/main/Source/EspNowBridge.h index f4eee05..d486ec2 100644 --- a/Apps/EspNowBridge/main/Source/EspNowBridge.h +++ b/Apps/EspNowBridge/main/Source/EspNowBridge.h @@ -9,6 +9,8 @@ #include +#include + #include /** RAII guard: pauses WifiService's background auto-connect scan for the guard's lifetime. See @@ -25,7 +27,13 @@ class AutoScanPauseGuard { struct Context { uint32_t appInstanceId; + // Set once in espNowBridgeInit(), reused by onUpdateButtonClicked() to bind pickFileStream + // when launching the file-selection app - must outlive ctx (see espNowBridgeInit()'s doc). + TaskEventGroup* eventGroup = nullptr; + uint32_t pickFileLaunchId = 0; + AppStream pickFileStream {}; + uint8_t pickFileBuffer[256] {}; std::string pendingUpdateFilePath; Device* wifiDevice = nullptr; WifiEventSubscription wifiEventSub {}; @@ -67,17 +75,24 @@ struct Context { /** Sets up state that must exist for the whole app instance lifetime, regardless of how many * times the window is (re)built. Call once, right after constructing the Context. - * @param eventGroup subscribes ctx's WiFi event subscription into this group; must outlive ctx - * (destructed only after espNowBridgeTeardown()). */ + * @param eventGroup stored on ctx (also used for the file-selection app's stream binding) and + * subscribes ctx's WiFi event subscription into this group; must outlive ctx (destructed only + * after espNowBridgeTeardown()). */ void espNowBridgeInit(Context* ctx, TaskEventGroup* eventGroup); /** Drains any WiFi events queued for ctx and reacts to them (radio/station state changes). * Call from the app's main loop after task_event_group_wait_any() returns. */ void espNowBridgeProcessWifiEvents(Context* ctx); -/** window_manager_create()'s WindowCreateWidgetsFn - @a userData is the Context* for this instance. */ +/** window_manager_create_ext()'s WindowCreateWidgetsFn - @a userData is the Context* for this instance. */ void espNowBridgeCreateWidgets(lv_obj_t* parent, void* userData); +/** window_manager_create_ext()'s WindowDestroyWidgetsFn - @a userData is the Context* for this + * instance. Clears isShown so dispatchToUi() stops touching this window's (now-deleted) + * lv_obj_t*s - fires whenever this window is buried (e.g. the file-selection dialog opening as + * a modal child), not just on final teardown. */ +void espNowBridgeDestroyWidgets(void* userData); + /** Starts the update task if ctx->pendingUpdateFilePath is set (consuming it). Called both from * within espNowBridgeCreateWidgets() and directly by main()'s event loop right after a picked * path arrives - the file-selection child's window teardown (and so this window's rebuild) diff --git a/Apps/EspNowBridge/main/Source/main.cpp b/Apps/EspNowBridge/main/Source/main.cpp index 8f753c4..02b0bfe 100644 --- a/Apps/EspNowBridge/main/Source/main.cpp +++ b/Apps/EspNowBridge/main/Source/main.cpp @@ -3,13 +3,12 @@ #include #include #include +#include #include #include -#include - #include extern "C" { @@ -31,7 +30,7 @@ int main(int argc, char* argv[]) { struct AppEventSubscription sub {}; check(app_event_subscribe(&sub, &event_group) == ERROR_NONE); - WindowId window = window_manager_create(app_instance_id, espNowBridgeCreateWidgets, ctx.get()); + WindowId window = window_manager_create_ext(app_instance_id, espNowBridgeCreateWidgets, espNowBridgeDestroyWidgets, ctx.get()); bool should_close = false; while (!should_close) { @@ -48,11 +47,18 @@ int main(int argc, char* argv[]) { if (event.result.launch_id == ctx->pickFileLaunchId) { ctx->pickFileLaunchId = 0; if (event.result.result == 0) { // 0 = Ok (see FileSelection.h) - char pathBuf[256] = {}; - if (tt_app_fileselection_get_result_path(pathBuf, sizeof(pathBuf))) { - ctx->pendingUpdateFilePath = pathBuf; + // ctx->pickFileBuffer is the stream's own backing storage (see + // onUpdateButtonClicked()'s AppStreamBinding), so it can't double as + // the read destination too. + char destination[sizeof(ctx->pickFileBuffer)]; + size_t length = app_stream_read(&ctx->pickFileStream, destination, sizeof(destination)); + app_stream_unsubscribe(&ctx->pickFileStream); + if (length > 0) { + ctx->pendingUpdateFilePath.assign(destination, length); espNowBridgeApplyPendingUpdate(ctx.get()); } + } else { + app_stream_unsubscribe(&ctx->pickFileStream); } } app_manager_stop(event.result.launch_id); diff --git a/Apps/EspNowBridge/manifest.properties b/Apps/EspNowBridge/manifest.properties index ef94329..33debe6 100644 --- a/Apps/EspNowBridge/manifest.properties +++ b/Apps/EspNowBridge/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32p4 app.id=tactility.espnowbridge -app.version.name=0.8.0 -app.version.code=8 +app.version.name=0.9.0 +app.version.code=9 app.name=ESP-NOW Bridge app.description=Companion app for updating P4 device C6 co-processor firmware to enable ESP-NOW bridge support. diff --git a/Apps/GPIO/CMakeLists.txt b/Apps/GPIO/CMakeLists.txt index af58077..4ae68e8 100644 --- a/Apps/GPIO/CMakeLists.txt +++ b/Apps/GPIO/CMakeLists.txt @@ -1,25 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(GPIO) -tactility_project(GPIO) - +tactility_project_pre(tactility.gpio) +project(tactility.gpio) +tactility_project_post(tactility.gpio) diff --git a/Apps/GPIO/main/CMakeLists.txt b/Apps/GPIO/main/CMakeLists.txt index c950070..4389729 100644 --- a/Apps/GPIO/main/CMakeLists.txt +++ b/Apps/GPIO/main/CMakeLists.txt @@ -1,9 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) - -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK driver -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} REQUIRES driver) diff --git a/Apps/GPIO/manifest.properties b/Apps/GPIO/manifest.properties index 74b62d8..3cb4d4a 100644 --- a/Apps/GPIO/manifest.properties +++ b/Apps/GPIO/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.gpio -app.version.name=0.13.0 -app.version.code=13 +app.version.name=0.14.0 +app.version.code=14 app.name=GPIO diff --git a/Apps/GraphicsDemo/CMakeLists.txt b/Apps/GraphicsDemo/CMakeLists.txt index b57599e..65ebd93 100644 --- a/Apps/GraphicsDemo/CMakeLists.txt +++ b/Apps/GraphicsDemo/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(GraphicsDemo) -tactility_project(GraphicsDemo) +tactility_project_pre(tactility.graphicsdemo) +project(tactility.graphicsdemo) +tactility_project_post(tactility.graphicsdemo) diff --git a/Apps/GraphicsDemo/main/CMakeLists.txt b/Apps/GraphicsDemo/main/CMakeLists.txt index 759aed7..6121d68 100644 --- a/Apps/GraphicsDemo/main/CMakeLists.txt +++ b/Apps/GraphicsDemo/main/CMakeLists.txt @@ -1,7 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRC_DIRS "Source" - INCLUDE_DIRS "Include" - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} INCLUDE_DIRS "Include") diff --git a/Apps/GraphicsDemo/manifest.properties b/Apps/GraphicsDemo/manifest.properties index 713ad6f..87db999 100644 --- a/Apps/GraphicsDemo/manifest.properties +++ b/Apps/GraphicsDemo/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.graphicsdemo -app.version.name=0.11.0 -app.version.code=11 +app.version.name=0.12.0 +app.version.code=12 app.name=Graphics Demo diff --git a/Apps/HelloWorld/CMakeLists.txt b/Apps/HelloWorld/CMakeLists.txt index fa0b687..70a314b 100644 --- a/Apps/HelloWorld/CMakeLists.txt +++ b/Apps/HelloWorld/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(HelloWorld) -tactility_project(HelloWorld) +tactility_project_pre(tactility.helloworld) +project(tactility.helloworld) +tactility_project_post(tactility.helloworld) diff --git a/Apps/HelloWorld/main/CMakeLists.txt b/Apps/HelloWorld/main/CMakeLists.txt index db2068e..2c68aad 100644 --- a/Apps/HelloWorld/main/CMakeLists.txt +++ b/Apps/HelloWorld/main/CMakeLists.txt @@ -1,6 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/HelloWorld/manifest.properties b/Apps/HelloWorld/manifest.properties index 71f0653..396c002 100644 --- a/Apps/HelloWorld/manifest.properties +++ b/Apps/HelloWorld/manifest.properties @@ -1,7 +1,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev -target.platforms=esp32,esp32s3,esp32c6,esp32p4 +target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 app.id=tactility.helloworld -app.version.name=0.11.0 -app.version.code=11 +app.version.name=0.12.0 +app.version.code=12 app.name=Hello World diff --git a/Apps/M5UnitTest/CMakeLists.txt b/Apps/M5UnitTest/CMakeLists.txt index 0b4273d..e15563e 100644 --- a/Apps/M5UnitTest/CMakeLists.txt +++ b/Apps/M5UnitTest/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(M5UnitTest) -tactility_project(M5UnitTest) +tactility_project_pre(tactility.m5unittest) +project(tactility.m5unittest) +tactility_project_post(tactility.m5unittest) diff --git a/Apps/M5UnitTest/main/CMakeLists.txt b/Apps/M5UnitTest/main/CMakeLists.txt index dc43276..e26f4f3 100644 --- a/Apps/M5UnitTest/main/CMakeLists.txt +++ b/Apps/M5UnitTest/main/CMakeLists.txt @@ -1,10 +1,5 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) -file(GLOB_RECURSE UNIT_MODULE_FILES ../../../Libraries/M5UnitModules/Source/*.c*) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} ${UNIT_MODULE_FILES} - # Library headers must be included directly, - # because all regular dependencies get stripped by elf_loader's cmake script - INCLUDE_DIRS ../../../Libraries/M5UnitModules/Include - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +file(GLOB_RECURSE UNIT_MODULE_FILES ../../../Libraries/M5UnitModules/Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} ${UNIT_MODULE_FILES} INCLUDE_DIRS ../../../Libraries/M5UnitModules/Include) diff --git a/Apps/M5UnitTest/manifest.properties b/Apps/M5UnitTest/manifest.properties index b774d6b..a3d606b 100644 --- a/Apps/M5UnitTest/manifest.properties +++ b/Apps/M5UnitTest/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 app.id=tactility.m5unittest -app.version.name=0.9.0 -app.version.code=9 +app.version.name=0.10.0 +app.version.code=10 app.name=M5 Unit Test diff --git a/Apps/Magic8Ball/CMakeLists.txt b/Apps/Magic8Ball/CMakeLists.txt index 25ab8be..f082795 100644 --- a/Apps/Magic8Ball/CMakeLists.txt +++ b/Apps/Magic8Ball/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Magic8Ball) -tactility_project(Magic8Ball) +tactility_project_pre(tactility.magic8ball) +project(tactility.magic8ball) +tactility_project_post(tactility.magic8ball) diff --git a/Apps/Magic8Ball/main/CMakeLists.txt b/Apps/Magic8Ball/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/Magic8Ball/main/CMakeLists.txt +++ b/Apps/Magic8Ball/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/Magic8Ball/manifest.properties b/Apps/Magic8Ball/manifest.properties index f18cef4..bfc52cd 100644 --- a/Apps/Magic8Ball/manifest.properties +++ b/Apps/Magic8Ball/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.magic8ball -app.version.name=0.10.0 -app.version.code=10 +app.version.name=0.11.0 +app.version.code=11 app.name=Magic 8-Ball diff --git a/Apps/MediaKeys/CMakeLists.txt b/Apps/MediaKeys/CMakeLists.txt index 577f588..9efe14a 100644 --- a/Apps/MediaKeys/CMakeLists.txt +++ b/Apps/MediaKeys/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(MediaKeys) -tactility_project(MediaKeys) +tactility_project_pre(tactility.mediakeys) +project(tactility.mediakeys) +tactility_project_post(tactility.mediakeys) diff --git a/Apps/MediaKeys/main/CMakeLists.txt b/Apps/MediaKeys/main/CMakeLists.txt index 3067e0d..2c68aad 100644 --- a/Apps/MediaKeys/main/CMakeLists.txt +++ b/Apps/MediaKeys/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/MediaKeys/main/Source/MediaKeys.cpp b/Apps/MediaKeys/main/Source/MediaKeys.cpp index 2f058ec..7c711d2 100644 --- a/Apps/MediaKeys/main/Source/MediaKeys.cpp +++ b/Apps/MediaKeys/main/Source/MediaKeys.cpp @@ -65,6 +65,7 @@ void sendKeyTask(void* param) { uint8_t release[2] = {0, 0}; bluetooth_hid_device_send_consumer(data->hidDevice, release, 2); + device_put(data->hidDevice); delete data; vTaskDelete(nullptr); } @@ -189,6 +190,7 @@ void handleBtEvent(Context* ctx, const BtEvent& event) { if (ctx->switchWidget) lv_obj_remove_state(ctx->switchWidget, LV_STATE_CHECKED); if (ctx->mainWrapper) lv_obj_add_flag(ctx->mainWrapper, LV_OBJ_FLAG_HIDDEN); } + if (ctx->hidDevice) device_put(ctx->hidDevice); ctx->hidDevice = nullptr; ctx->isEnabled = false; ctx->radioEnabling = false; @@ -207,7 +209,7 @@ void startHid(Context* ctx) { // May be called from handleBtEvent() on the app's own task - LVGL must already be locked by caller. ctx->radioEnabling = false; - ctx->hidDevice = bluetooth_hid_device_get_device(); + ctx->hidDevice = bluetooth_hid_device_get(); if (!ctx->hidDevice) { LOG_E(TAG, "BLE HID device unavailable after radio on"); ctx->isEnabled = false; @@ -218,6 +220,7 @@ void startHid(Context* ctx) { error_t err = bluetooth_hid_device_start(ctx->hidDevice, BT_HID_DEVICE_MODE_KEYBOARD); if (err != ERROR_NONE) { LOG_E(TAG, "Failed to start HID device: %d", (int)err); + device_put(ctx->hidDevice); ctx->hidDevice = nullptr; ctx->isEnabled = false; if (ctx->switchWidget) lv_obj_remove_state(ctx->switchWidget, LV_STATE_CHECKED); @@ -248,6 +251,7 @@ void teardownBt(Context* ctx) { // Documentation/bluetooth-app-migration.md) - just restore the radio and drop our ref. restoreRadioIfNeeded(ctx); if (ctx->btDevice) device_put(ctx->btDevice); + if (ctx->hidDevice) device_put(ctx->hidDevice); ctx->btDevice = nullptr; ctx->hidDevice = nullptr; } @@ -288,7 +292,10 @@ void handleSwitchToggle(Context* ctx, bool enabled) { if (device_has_active_by_type(&KEYBOARD_TYPE)) exitKeyMode(ctx); // Explicit user toggle-off: stop HID cleanly (safe here since we're on the // LVGL task and the user intentionally disabled, so no race with app teardown). - if (ctx->hidDevice) bluetooth_hid_device_stop(ctx->hidDevice); + if (ctx->hidDevice) { + bluetooth_hid_device_stop(ctx->hidDevice); + device_put(ctx->hidDevice); + } ctx->hidDevice = nullptr; restoreRadioIfNeeded(ctx); if (ctx->mainWrapper) lv_obj_add_flag(ctx->mainWrapper, LV_OBJ_FLAG_HIDDEN); @@ -305,9 +312,17 @@ void handleButtonPress(Context* ctx, uint32_t buttonId) { LOG_I(TAG, "Button %lu pressed", buttonId); + // The task runs across a 50ms delay outside the caller's stack frame - it needs its own + // reference so teardownBt()'s device_put() can't invalidate ctx->hidDevice out from under it. + if (device_get(ctx->hidDevice) != ERROR_NONE) { + LOG_E(TAG, "Failed to acquire HID device reference for send task"); + return; + } + SendKeyData* data = new SendKeyData {ctx->hidDevice, CONSUMER_USAGE[buttonId]}; if (xTaskCreate(sendKeyTask, "bt_key", 4096, data, tskIDLE_PRIORITY + 1, nullptr) != pdPASS) { LOG_E(TAG, "Failed to create send task"); + device_put(data->hidDevice); delete data; } } diff --git a/Apps/MediaKeys/manifest.properties b/Apps/MediaKeys/manifest.properties index 17ed6fd..cd6f956 100644 --- a/Apps/MediaKeys/manifest.properties +++ b/Apps/MediaKeys/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 app.id=tactility.mediakeys -app.version.name=0.11.0 -app.version.code=11 +app.version.name=0.12.0 +app.version.code=12 app.name=Media Keys app.description=Bluetooth media keys. Touch or Physical Keyboard control\nB - previous, P - play/pause, N - next, M - mute, D - volume down, U - volume up.\nQ or ESC to exit focus. diff --git a/Apps/MystifyDemo/CMakeLists.txt b/Apps/MystifyDemo/CMakeLists.txt index c1fc97d..ea4379a 100644 --- a/Apps/MystifyDemo/CMakeLists.txt +++ b/Apps/MystifyDemo/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(MystifyDemo) -tactility_project(MystifyDemo) +tactility_project_pre(tactility.mystifydemo) +project(tactility.mystifydemo) +tactility_project_post(tactility.mystifydemo) diff --git a/Apps/MystifyDemo/main/CMakeLists.txt b/Apps/MystifyDemo/main/CMakeLists.txt index 759aed7..6121d68 100644 --- a/Apps/MystifyDemo/main/CMakeLists.txt +++ b/Apps/MystifyDemo/main/CMakeLists.txt @@ -1,7 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRC_DIRS "Source" - INCLUDE_DIRS "Include" - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES} INCLUDE_DIRS "Include") diff --git a/Apps/MystifyDemo/manifest.properties b/Apps/MystifyDemo/manifest.properties index b4a2373..7e36cf5 100644 --- a/Apps/MystifyDemo/manifest.properties +++ b/Apps/MystifyDemo/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.mystifydemo -app.version.name=0.12.0 -app.version.code=12 +app.version.name=0.13.0 +app.version.code=13 app.name=Mystify Demo diff --git a/Apps/SerialConsole/CMakeLists.txt b/Apps/SerialConsole/CMakeLists.txt index 035334d..c01c4f2 100644 --- a/Apps/SerialConsole/CMakeLists.txt +++ b/Apps/SerialConsole/CMakeLists.txt @@ -1,25 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(SerialConsole) -tactility_project(SerialConsole) - +tactility_project_pre(tactility.serialconsole) +project(tactility.serialconsole) +tactility_project_post(tactility.serialconsole) diff --git a/Apps/SerialConsole/main/CMakeLists.txt b/Apps/SerialConsole/main/CMakeLists.txt index 94136e8..2c68aad 100644 --- a/Apps/SerialConsole/main/CMakeLists.txt +++ b/Apps/SerialConsole/main/CMakeLists.txt @@ -1,9 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) - -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/SerialConsole/manifest.properties b/Apps/SerialConsole/manifest.properties index 44b6195..cfbdb43 100644 --- a/Apps/SerialConsole/manifest.properties +++ b/Apps/SerialConsole/manifest.properties @@ -2,6 +2,6 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.serialconsole -app.version.name=0.13.0 -app.version.code=13 +app.version.name=0.14.0 +app.version.code=14 app.name=Serial Console diff --git a/Apps/Snake/CMakeLists.txt b/Apps/Snake/CMakeLists.txt index 193f4d4..952cc55 100644 --- a/Apps/Snake/CMakeLists.txt +++ b/Apps/Snake/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(Snake) -tactility_project(Snake) +tactility_project_pre(tactility.snake) +project(tactility.snake) +tactility_project_post(tactility.snake) diff --git a/Apps/Snake/main/CMakeLists.txt b/Apps/Snake/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/Snake/main/CMakeLists.txt +++ b/Apps/Snake/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/Snake/manifest.properties b/Apps/Snake/manifest.properties index 394169d..75bb565 100644 --- a/Apps/Snake/manifest.properties +++ b/Apps/Snake/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.snake -app.version.name=0.14.0 -app.version.code=14 +app.version.name=0.15.0 +app.version.code=15 app.name=Snake app.description=Classic Snake game diff --git a/Apps/TamaTac/CMakeLists.txt b/Apps/TamaTac/CMakeLists.txt index 3257e71..1ec190b 100644 --- a/Apps/TamaTac/CMakeLists.txt +++ b/Apps/TamaTac/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(TamaTac) -tactility_project(TamaTac) +tactility_project_pre(tactility.tamatac) +project(tactility.tamatac) +tactility_project_post(tactility.tamatac) diff --git a/Apps/TamaTac/main/CMakeLists.txt b/Apps/TamaTac/main/CMakeLists.txt index 531d730..e7f8231 100644 --- a/Apps/TamaTac/main/CMakeLists.txt +++ b/Apps/TamaTac/main/CMakeLists.txt @@ -1,10 +1,6 @@ +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") + file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SFX_ENGINE_FILES ../../../Libraries/SfxEngine/Source/*.c*) -idf_component_register( - SRCS ${SOURCE_FILES} ${SFX_ENGINE_FILES} - # Library headers must be included directly, - # because all regular dependencies get stripped by elf_loader's cmake script - INCLUDE_DIRS ../../../Libraries/SfxEngine/Include - REQUIRES TactilitySDK -) +tactility_component_register(SRCS ${SOURCE_FILES} ${SFX_ENGINE_FILES} INCLUDE_DIRS ../../../Libraries/SfxEngine/Include) diff --git a/Apps/TamaTac/manifest.properties b/Apps/TamaTac/manifest.properties index b0290ca..46684df 100644 --- a/Apps/TamaTac/manifest.properties +++ b/Apps/TamaTac/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.tamatac -app.version.name=0.9.0 -app.version.code=9 +app.version.name=0.10.0 +app.version.code=10 app.name=TamaTac app.description=Virtual pet inspired by Tamagotchi. Only runs on devices with PSRAM. diff --git a/Apps/TodoList/CMakeLists.txt b/Apps/TodoList/CMakeLists.txt index 689be32..f92028f 100644 --- a/Apps/TodoList/CMakeLists.txt +++ b/Apps/TodoList/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(TodoList) -tactility_project(TodoList) +tactility_project_pre(tactility.todolist) +project(tactility.todolist) +tactility_project_post(tactility.todolist) diff --git a/Apps/TodoList/main/CMakeLists.txt b/Apps/TodoList/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/TodoList/main/CMakeLists.txt +++ b/Apps/TodoList/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/TodoList/manifest.properties b/Apps/TodoList/manifest.properties index b37a692..5a7f803 100644 --- a/Apps/TodoList/manifest.properties +++ b/Apps/TodoList/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.todolist -app.version.name=0.12.0 -app.version.code=12 +app.version.name=0.13.0 +app.version.code=13 app.name=Todo List app.description=Simple task list manager diff --git a/Apps/TwoEleven/CMakeLists.txt b/Apps/TwoEleven/CMakeLists.txt index 3bd0e05..5808671 100644 --- a/Apps/TwoEleven/CMakeLists.txt +++ b/Apps/TwoEleven/CMakeLists.txt @@ -1,24 +1,13 @@ +# tactility-cmakelists-version: 1 cmake_minimum_required(VERSION 3.20) -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -if (DEFINED ENV{TACTILITY_SDK_PATH}) - set(TACTILITY_SDK_PATH $ENV{TACTILITY_SDK_PATH}) -else() - set(TACTILITY_SDK_PATH "../../release/TactilitySDK") - message(WARNING "⚠️ TACTILITY_SDK_PATH environment variable is not set, defaulting to ${TACTILITY_SDK_PATH}") +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") endif() +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") -# Must be set before project() - ESP-IDF resolves components at that point, so setting these -# from inside the tactility_project() macro (which necessarily runs after project(), since it -# also calls project_elf()) would be too late. -set(EXTRA_COMPONENT_DIRS - ${TACTILITY_SDK_PATH} - "${TACTILITY_SDK_PATH}/Libraries/TactilityFreeRtos" - "${TACTILITY_SDK_PATH}/Modules" -) - -project(TwoEleven) -tactility_project(TwoEleven) +tactility_project_pre(tactility.twoeleven) +project(tactility.twoeleven) +tactility_project_post(tactility.twoeleven) diff --git a/Apps/TwoEleven/main/CMakeLists.txt b/Apps/TwoEleven/main/CMakeLists.txt index 3d04446..2c68aad 100644 --- a/Apps/TwoEleven/main/CMakeLists.txt +++ b/Apps/TwoEleven/main/CMakeLists.txt @@ -1,8 +1,4 @@ -file(GLOB_RECURSE SOURCE_FILES - Source/*.c* -) +include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake") -idf_component_register( - SRCS ${SOURCE_FILES} - REQUIRES TactilitySDK -) +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) +tactility_component_register(SRCS ${SOURCE_FILES}) \ No newline at end of file diff --git a/Apps/TwoEleven/manifest.properties b/Apps/TwoEleven/manifest.properties index dd0e55a..4a7c7a1 100644 --- a/Apps/TwoEleven/manifest.properties +++ b/Apps/TwoEleven/manifest.properties @@ -2,7 +2,7 @@ manifest.version=0.2 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 app.id=tactility.twoeleven -app.version.name=0.13.0 -app.version.code=13 +app.version.name=0.14.0 +app.version.code=14 app.name=2048 app.description=A fun, customizable 2048 sliding tile game for tactility!\nSlide tiles to combine numbers and reach 2048.\nChoose grid sizes: 3x3 (easy), 4x4 (classic), 5x5, or 6x6 (expert). diff --git a/tactility.py b/tactility.py index 547f08f..91d6c2f 100644 --- a/tactility.py +++ b/tactility.py @@ -12,7 +12,7 @@ from urllib.parse import urlparse ttbuild_path = ".tactility" -ttbuild_version = "4.2.0" +ttbuild_version = "5.0.1" ttbuild_cdn = "https://cdn.tactilityproject.org" ttbuild_sdk_json_validity = 3600 # seconds ttport = 6666 @@ -140,9 +140,13 @@ def get_sdk_dir(version, platform): sdk_dir = os.path.join(sdk_parent_dir, "TactilitySDK") if not os.path.isdir(sdk_dir): exit_with_error(f"Local SDK folder not found for platform {platform}: {sdk_dir}") - return sdk_dir + return os.path.abspath(sdk_dir) else: - return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK") + # Must be absolute: this is exported as $TACTILITY_SDK_PATH and included by each app's + # main/CMakeLists.txt, which ESP-IDF also re-processes in a separate `cmake -P` subprocess + # (tools/cmake/scripts/component_get_requirements.cmake) with its own working directory - + # a relative path here resolves against whatever CWD that subprocess happens to have. + return os.path.abspath(os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK")) def validate_local_sdks(platforms, version): if not use_local_sdk: @@ -187,24 +191,26 @@ def update_tool_json(): def should_fetch_sdkconfig_files(platform_targets): for platform in platform_targets: - sdkconfig_filename = f"sdkconfig.app.{platform}" - if not os.path.exists(os.path.join(ttbuild_path, sdkconfig_filename)): - return True + if not platform.startswith("posix"): + sdkconfig_filename = f"sdkconfig.app.{platform}" + if not os.path.exists(os.path.join(ttbuild_path, sdkconfig_filename)): + return True return False def fetch_sdkconfig_files(platform_targets): for platform in platform_targets: - sdkconfig_filename = f"sdkconfig.app.{platform}" - target_path = os.path.join(ttbuild_path, sdkconfig_filename) - if not download_file(f"{ttbuild_cdn}/sdk/{sdkconfig_filename}", target_path): - exit_with_error(f"Failed to download sdkconfig file for {platform}") + if not platform.startswith("posix"): + sdkconfig_filename = f"sdkconfig.app.{platform}" + target_path = os.path.join(ttbuild_path, sdkconfig_filename) + if not download_file(f"{ttbuild_cdn}/sdk/{sdkconfig_filename}", target_path): + exit_with_error(f"Failed to download sdkconfig file for {platform}") #endregion SDK helpers #region Validation -def validate_environment(): - if os.environ.get("IDF_PATH") is None: +def validate_environment(platforms): + if any(not platform.startswith("posix") for platform in platforms) and os.environ.get("IDF_PATH") is None: if sys.platform == "win32": exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via %IDF_PATH%\\export.ps1") else: @@ -315,6 +321,44 @@ def sdk_download_all(version, platforms): #endregion SDK download +#region CMakeLists scaffolding + +# Bump whenever CMAKELISTS_TEMPLATE below changes, so existing apps' generated CMakeLists.txt get +# regenerated on their next build instead of silently going stale. +CMAKELISTS_VERSION = 1 + +CMAKELISTS_TEMPLATE = """# tactility-cmakelists-version: %(version)d +cmake_minimum_required(VERSION 3.20) + +if (NOT DEFINED ENV{TACTILITY_SDK_PATH}) + message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set") +endif() + +get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE) +include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") + +tactility_project_pre(%(app_id)s) +project(%(app_id)s) +tactility_project_post(%(app_id)s) +""" + +def cmakelists_version_marker(): + return f"# tactility-cmakelists-version: {CMAKELISTS_VERSION}" + +def ensure_cmakelists_up_to_date(manifest): + marker = cmakelists_version_marker() + if os.path.exists("CMakeLists.txt"): + with open("CMakeLists.txt", "r") as file: + first_line = file.readline().rstrip("\n") + if first_line == marker: + return + print(f"Updating CMakeLists.txt to {marker}") + content = CMAKELISTS_TEMPLATE % {"version": CMAKELISTS_VERSION, "app_id": manifest["app.id"]} + with open("CMakeLists.txt", "w") as file: + file.write(content) + +#endregion CMakeLists scaffolding + #region Building def get_cmake_path(platform): @@ -322,18 +366,65 @@ def get_cmake_path(platform): def find_elf_file(platform): cmake_dir = get_cmake_path(platform) - if os.path.exists(cmake_dir): - for file in os.listdir(cmake_dir): - if file.endswith(".app.elf"): - return os.path.join(cmake_dir, file) + if not os.path.exists(cmake_dir): + return None + # POSIX apps are dlopen()ed shared objects (app-posix-module), not idf.py/elf_loader + # relocatable images, so they land as a plain ".so" instead of "*.app.elf". + suffix = ".so" if platform.startswith("posix") else ".app.elf" + for file in os.listdir(cmake_dir): + if file.endswith(suffix): + return os.path.join(cmake_dir, file) return None +def get_posix_build_env(sdk_dir): + # A dev shell may already have ESP-IDF's export.sh sourced; strip it so the SDK's plain-CMake + # top-level CMakeLists.txt takes the POSIX branch instead of the idf.py one. + env = os.environ.copy() + env.pop("ESP_IDF_VERSION", None) + env.pop("IDF_PATH", None) + env["TACTILITY_SDK_PATH"] = sdk_dir + return env + +def build_posix(version, platform, skip_build): + sdk_dir = get_sdk_dir(version, platform) + if verbose: + print(f"Using SDK at {sdk_dir}") + if skip_build: + return True + env = get_posix_build_env(sdk_dir) + cmake_path = get_cmake_path(platform) + print_status_busy(f"Building {platform}") + configure_command = ["cmake", "-S", ".", "-B", cmake_path, "-G", "Ninja"] + if verbose: + print(f"Running command: {' '.join(configure_command)}") + configure_result = subprocess.run(configure_command, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if configure_result.returncode != 0: + print(configure_result.stdout.decode("UTF-8"), end="") + print_status_error(f"Configuring {platform}") + return False + build_command = ["cmake", "--build", cmake_path] + if verbose: + print(f"Running command: {' '.join(build_command)}") + with subprocess.Popen(build_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) as process: + build_output = wait_for_process(process) + if process.returncode == 0: + print_status_success(f"Building {platform}") + return True + else: + for line in build_output: + print(line, end="") + print_status_error(f"Building {platform}") + return False + def build_all(version, platforms, skip_build): for platform in platforms: + if platform.startswith("posix"): + if not build_posix(version, platform, skip_build): + return False # First build command must be "idf.py build", otherwise it fails to execute "idf.py elf" # We check if the ELF file exists and run the correct command # This can lead to code caching issues, so sometimes a clean build is required - if find_elf_file(platform) is None: + elif find_elf_file(platform) is None: if not build_first(version, platform, skip_build): return False else: @@ -372,7 +463,8 @@ def build_first(version, platform, skip_build): print(f"Using SDK at {sdk_dir}") os.environ["TACTILITY_SDK_PATH"] = sdk_dir sdkconfig_path = os.path.join(ttbuild_path, f"sdkconfig.app.{platform}") - shutil.copy(sdkconfig_path, "sdkconfig") + if not platform.startswith("posix"): + shutil.copy(sdkconfig_path, "sdkconfig") elf_path = find_elf_file(platform) # Remove previous elf file: re-creation of the file is used to measure if the build succeeded, # as the actual build job will always fail due to technical issues with the elf cmake script @@ -448,7 +540,10 @@ def package_intermediate_binaries(target_path, platforms): if elf_path is None: print_error(f"ELF file not found for {platform}") return False - shutil.copy(elf_path, os.path.join(elf_dir, f"{platform}.elf")) + # app-posix-module's loader resolves an installed app to "elf/posix-.so", matching + # its own compile-time architecture, not "*.elf". + extension = ".so" if platform.startswith("posix") else ".elf" + shutil.copy(elf_path, os.path.join(elf_dir, f"{platform}{extension}")) return True def package_intermediate_assets(target_path): @@ -467,12 +562,10 @@ def package_intermediate(platforms): package_intermediate_assets(target_path) return True -def package_name(platforms): - elf_path = find_elf_file(platforms[0]) - elf_base_name = os.path.basename(elf_path).removesuffix(".app.elf") - return os.path.join("build", f"{elf_base_name}.app") +def package_name(manifest): + return os.path.join("build", f"{manifest['app.id']}.app") -def package_all(platforms): +def package_all(manifest, platforms): status = f"Building package with {platforms}" print_status_busy(status) if not package_intermediate(platforms): @@ -480,7 +573,7 @@ def package_all(platforms): return False # Create build/something.app try: - tar_path = package_name(platforms) + tar_path = package_name(manifest) with tarfile.open(tar_path, mode="w", format=tarfile.USTAR_FORMAT) as tar: tar.add(os.path.join("build", "package-intermediate"), arcname="") print_status_success(status) @@ -496,10 +589,11 @@ def setup_environment(): os.makedirs(ttbuild_path, exist_ok=True) def build_action(manifest, platform_arg, skip_build): - # Environment validation - validate_environment() + ensure_cmakelists_up_to_date(manifest) platforms_to_build = get_manifest_target_platforms(manifest, platform_arg) - + # Environment validation + validate_environment(platforms_to_build) + if use_local_sdk: global local_base_path local_base_path = os.environ.get("TACTILITY_SDK_PATH") @@ -519,7 +613,7 @@ def build_action(manifest, platform_arg, skip_build): if not build_all(sdk_version, platforms_to_build, skip_build): # Environment validation return False if not skip_build: - if not package_all(platforms_to_build): + if not package_all(manifest, platforms_to_build): return False return True @@ -574,14 +668,14 @@ def run_action(manifest, ip): except requests.RequestException as e: print_status_error(f"Running request failed: {e}") -def install_action(ip, platforms): +def install_action(manifest, ip, platforms): print_status_busy("Installing") for platform in platforms: elf_path = find_elf_file(platform) if elf_path is None: print_status_error(f"ELF file not built for {platform}") return False - package_path = package_name(platforms) + package_path = package_name(manifest) # print(f"Installing {package_path} to {ip}") url = get_url(ip, "/app/install") try: @@ -695,7 +789,7 @@ def uninstall_action(manifest, ip): if len(sys.argv) >= 4: platform = sys.argv[3] platforms_to_install = [platform] - install_action(sys.argv[2], platforms_to_install) + install_action(manifest, sys.argv[2], platforms_to_install) elif action_arg == "uninstall": if len(sys.argv) < 3: print_help() @@ -711,7 +805,7 @@ def uninstall_action(manifest, ip): platform = sys.argv[3] platforms_to_install = [platform] if build_action(manifest, platform, skip_build): - if install_action(sys.argv[2], platforms_to_install): + if install_action(manifest, sys.argv[2], platforms_to_install): run_action(manifest, sys.argv[2]) else: print_help()