From 8f95f7e31eafa80e24b156d046a75429a77b8bb6 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 20:37:49 +0100 Subject: [PATCH 01/11] Fix TactilitySDK Add missing binary for LVGL --- Buildscripts/release-sdk.py | 4 ++-- Documentation/ideas.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Buildscripts/release-sdk.py b/Buildscripts/release-sdk.py index a033836b5..bb7def306 100644 --- a/Buildscripts/release-sdk.py +++ b/Buildscripts/release-sdk.py @@ -92,10 +92,10 @@ def main(): {'src': 'Modules/lvgl-module/CMakeLists.txt', 'dst': 'Libraries/lvgl-module/'}, {'src': 'Modules/lvgl-module/LICENSE*.*', 'dst': 'Libraries/lvgl-module/'}, # lvgl (basics) - {'src': 'build/esp-idf/lvgl/liblvgl.a', 'dst': 'Libraries/lvgl/Binary/'}, + {'src': 'build/esp-idf/lvgl__lvgl/liblvgl__lvgl.a', 'dst': 'Libraries/lvgl/Binary/liblvgl.a'}, {'src': 'Libraries/lvgl/lvgl.h', 'dst': 'Libraries/lvgl/Include/'}, {'src': 'Libraries/lvgl/lv_version.h', 'dst': 'Libraries/lvgl/Include/'}, - {'src': 'Libraries/lvgl/LICENCE.txt', 'dst': 'Libraries/lvgl/LICENSE.txt'}, + {'src': 'Libraries/lvgl/LICENCE*.*', 'dst': 'Libraries/lvgl/'}, {'src': 'Libraries/lvgl/src/lv_conf_kconfig.h', 'dst': 'Libraries/lvgl/Include/lv_conf.h'}, {'src': 'Libraries/lvgl/src/**/*.h', 'dst': 'Libraries/lvgl/Include/src/'}, # elf_loader diff --git a/Documentation/ideas.md b/Documentation/ideas.md index bdbcbb6e9..cbc9a2d96 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -11,6 +11,7 @@ ## Higher Priority +- Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module. - Add kernel listening mechanism so that the root device init can be notified when a device becomes available: Callback for device/start stop with filtering on device type: - on_before_start: e.g. to do the CS pin hack for SD card on a shared bus From 8923a7a123fc2fd2cdf20db293457dc226feacd4 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 20:52:22 +0100 Subject: [PATCH 02/11] Add app integration test and fix .gitignore --- .github/actions/build-sdk/action.yml | 8 + .gitignore | 5 +- Tests/HelloWorld/CMakeLists.txt | 16 + Tests/HelloWorld/main/CMakeLists.txt | 6 + Tests/HelloWorld/main/Source/main.c | 18 + Tests/HelloWorld/manifest.properties | 10 + Tests/HelloWorld/tactility.py | 693 +++++++++++++++++++++++++++ 7 files changed, 754 insertions(+), 2 deletions(-) create mode 100644 Tests/HelloWorld/CMakeLists.txt create mode 100644 Tests/HelloWorld/main/CMakeLists.txt create mode 100644 Tests/HelloWorld/main/Source/main.c create mode 100644 Tests/HelloWorld/manifest.properties create mode 100644 Tests/HelloWorld/tactility.py diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index a2c173f4f..f5d192268 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -30,6 +30,14 @@ runs: # NOTE: Update with ESP-IDF! ESP_IDF_VERSION: '5.5' run: python Buildscripts/release-sdk.py release/TactilitySDK + - name: 'Test Integration' + shell: bash + working-directory: Tests/HelloWorld + env: + # NOTE: Update with ESP-IDF! + ESP_IDF_VERSION: '5.5' + TACTILITY_SDK_PATH: '../../release/TactilitySDK' + run: python.py tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index eb05fa36e..38bcae999 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ build/ buildsim/ -build-*/ cmake-*/ CMakeCache.txt *.cbp @@ -21,4 +20,6 @@ dependencies.lock *.code-workspace .gitpod.yml -sdkconfig.board.*.dev \ No newline at end of file +sdkconfig.board.*.dev + +.tactility/ \ No newline at end of file diff --git a/Tests/HelloWorld/CMakeLists.txt b/Tests/HelloWorld/CMakeLists.txt new file mode 100644 index 000000000..95d0cb2e4 --- /dev/null +++ b/Tests/HelloWorld/CMakeLists.txt @@ -0,0 +1,16 @@ +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}") +endif() + +include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") +set(EXTRA_COMPONENT_DIRS ${TACTILITY_SDK_PATH}) + +project(HelloWorld) +tactility_project(HelloWorld) diff --git a/Tests/HelloWorld/main/CMakeLists.txt b/Tests/HelloWorld/main/CMakeLists.txt new file mode 100644 index 000000000..db2068e9b --- /dev/null +++ b/Tests/HelloWorld/main/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c) + +idf_component_register( + SRCS ${SOURCE_FILES} + REQUIRES TactilitySDK +) diff --git a/Tests/HelloWorld/main/Source/main.c b/Tests/HelloWorld/main/Source/main.c new file mode 100644 index 000000000..2f7459cdd --- /dev/null +++ b/Tests/HelloWorld/main/Source/main.c @@ -0,0 +1,18 @@ +#include +#include + +static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) { + lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t* label = lv_label_create(parent); + lv_label_set_text(label, "Hello, world!"); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); +} + +int main(int argc, char* argv[]) { + tt_app_register((AppRegistration) { + .onShow = onShowApp + }); + return 0; +} diff --git a/Tests/HelloWorld/manifest.properties b/Tests/HelloWorld/manifest.properties new file mode 100644 index 000000000..fa385e8c7 --- /dev/null +++ b/Tests/HelloWorld/manifest.properties @@ -0,0 +1,10 @@ +[manifest] +version=0.1 +[target] +sdk=0.7.0-dev +platforms=esp32,esp32s3,esp32c6,esp32p4 +[app] +id=one.tactility.helloworldtest +versionName=0.1.0 +versionCode=1 +name=Hello World diff --git a/Tests/HelloWorld/tactility.py b/Tests/HelloWorld/tactility.py new file mode 100644 index 000000000..d27bef4bf --- /dev/null +++ b/Tests/HelloWorld/tactility.py @@ -0,0 +1,693 @@ +import configparser +import json +import os +import re +import shutil +import sys +import subprocess +import time +import urllib.request +import zipfile +import requests +import tarfile + +ttbuild_path = ".tactility" +ttbuild_version = "3.2.0" +ttbuild_cdn = "https://cdn.tactilityproject.org" +ttbuild_sdk_json_validity = 3600 # seconds +ttport = 6666 +verbose = False +use_local_sdk = False +local_base_path = None + +shell_color_red = "\033[91m" +shell_color_orange = "\033[93m" +shell_color_green = "\033[32m" +shell_color_purple = "\033[35m" +shell_color_cyan = "\033[36m" +shell_color_reset = "\033[m" + +def print_help(): + print("Usage: python tactility.py [action] [options]") + print("") + print("Actions:") + print(" build [esp32,esp32s3] Build the app. Optionally specify a platform.") + print(" esp32: ESP32") + print(" esp32s3: ESP32 S3") + print(" clean Clean the build folders") + print(" clearcache Clear the SDK cache") + print(" updateself Update this tool") + print(" run [ip] Run the application") + print(" install [ip] Install the application") + print(" uninstall [ip] Uninstall the application") + print(" bir [ip] [esp32,esp32s3] Build, install then run. Optionally specify a platform.") + print(" brrr [ip] [esp32,esp32s3] Functionally the same as \"bir\", but \"app goes brrr\" meme variant.") + print("") + print("Options:") + print(" --help Show this commandline info") + print(" --local-sdk Use SDK specified by environment variable TACTILITY_SDK_PATH with platform subfolders matching target platforms.") + print(" --skip-build Run everything except the idf.py/CMake commands") + print(" --verbose Show extra console output") + +# region Core + +def download_file(url, filepath): + global verbose + if verbose: + print(f"Downloading from {url} to {filepath}") + request = urllib.request.Request( + url, + data=None, + headers={ + "User-Agent": f"Tactility Build Tool {ttbuild_version}" + } + ) + try: + response = urllib.request.urlopen(request) + file = open(filepath, mode="wb") + file.write(response.read()) + file.close() + return True + except OSError as error: + if verbose: + print_error(f"Failed to fetch URL {url}\n{error}") + return False + +def print_warning(message): + print(f"{shell_color_orange}WARNING: {message}{shell_color_reset}") + +def print_error(message): + print(f"{shell_color_red}ERROR: {message}{shell_color_reset}") + +def print_status_busy(status): + sys.stdout.write(f"⌛ {status}\r") + +def print_status_success(status): + # Trailing spaces are to overwrite previously written characters by a potentially shorter print_status_busy() text + print(f"✅ {shell_color_green}{status}{shell_color_reset} ") + +def print_status_error(status): + # Trailing spaces are to overwrite previously written characters by a potentially shorter print_status_busy() text + print(f"❌ {shell_color_red}{status}{shell_color_reset} ") + +def exit_with_error(message): + print_error(message) + sys.exit(1) + +def get_url(ip, path): + return f"http://{ip}:{ttport}{path}" + +def read_properties_file(path): + config = configparser.RawConfigParser() + config.read(path) + return config + +#endregion Core + +#region SDK helpers + +def read_sdk_json(): + json_file_path = os.path.join(ttbuild_path, "tool.json") + with open(json_file_path) as json_file: + return json.load(json_file) + +def get_sdk_dir(version, platform): + global use_local_sdk, local_base_path + if use_local_sdk: + base_path = local_base_path + if base_path is None: + exit_with_error("TACTILITY_SDK_PATH environment variable is not set") + sdk_parent_dir = os.path.join(base_path, f"{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 + else: + return os.path.join(ttbuild_path, f"{version}-{platform}", "TactilitySDK") + +def validate_local_sdks(platforms, version): + if not use_local_sdk: + return + global local_base_path + base_path = local_base_path + for platform in platforms: + sdk_parent_dir = os.path.join(base_path, f"{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 missing for {platform}: {sdk_dir}") + +def get_sdk_root_dir(version, platform): + global ttbuild_cdn + return os.path.join(ttbuild_path, f"{version}-{platform}") + +def get_sdk_url(version, file): + global ttbuild_cdn + return f"{ttbuild_cdn}/sdk/{version}/{file}" + +def sdk_exists(version, platform): + sdk_dir = get_sdk_dir(version, platform) + return os.path.isdir(sdk_dir) + +def should_update_tool_json(): + global ttbuild_cdn + json_filepath = os.path.join(ttbuild_path, "tool.json") + if os.path.exists(json_filepath): + json_modification_time = os.path.getmtime(json_filepath) + now = time.time() + global ttbuild_sdk_json_validity + minimum_seconds_difference = ttbuild_sdk_json_validity + return (now - json_modification_time) > minimum_seconds_difference + else: + return True + +def update_tool_json(): + global ttbuild_cdn, ttbuild_path + json_url = f"{ttbuild_cdn}/sdk/tool.json" + json_filepath = os.path.join(ttbuild_path, "tool.json") + return download_file(json_url, json_filepath) + +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 + 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}/{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: + 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: + exit_with_error("Cannot find the Espressif IDF SDK. Ensure it is installed and that it is activated via $PATH_TO_IDF_SDK/export.sh") + if not os.path.exists("manifest.properties"): + exit_with_error("manifest.properties not found") + if use_local_sdk == False and os.environ.get("TACTILITY_SDK_PATH") is not None: + print_warning("TACTILITY_SDK_PATH is set, but will be ignored by this command.") + print_warning("If you want to use it, use the '--local-sdk' parameter") + elif use_local_sdk == True and os.environ.get("TACTILITY_SDK_PATH") is None: + exit_with_error("local build was requested, but TACTILITY_SDK_PATH environment variable is not set.") + +def validate_self(sdk_json): + if not "toolVersion" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolVersion not found)") + if not "toolCompatibility" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolCompatibility not found)") + if not "toolDownloadUrl" in sdk_json: + exit_with_error("Server returned invalid SDK data format (toolDownloadUrl not found)") + tool_version = sdk_json["toolVersion"] + tool_compatibility = sdk_json["toolCompatibility"] + if tool_version != ttbuild_version: + print_warning(f"New version available: {tool_version} (currently using {ttbuild_version})") + print_warning(f"Run 'tactility.py updateself' to update.") + if re.search(tool_compatibility, ttbuild_version) is None: + print_error("The tool is not compatible anymore.") + print_error("Run 'tactility.py updateself' to update.") + sys.exit(1) + +#endregion Validation + +#region Manifest + +def read_manifest(): + return read_properties_file("manifest.properties") + +def validate_manifest(manifest): + # [manifest] + if not "manifest" in manifest: + exit_with_error("Invalid manifest format: [manifest] not found") + if not "version" in manifest["manifest"]: + exit_with_error("Invalid manifest format: [manifest] version not found") + # [target] + if not "target" in manifest: + exit_with_error("Invalid manifest format: [target] not found") + if not "sdk" in manifest["target"]: + exit_with_error("Invalid manifest format: [target] sdk not found") + if not "platforms" in manifest["target"]: + exit_with_error("Invalid manifest format: [target] platforms not found") + # [app] + if not "app" in manifest: + exit_with_error("Invalid manifest format: [app] not found") + if not "id" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] id not found") + if not "versionName" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] versionName not found") + if not "versionCode" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] versionCode not found") + if not "name" in manifest["app"]: + exit_with_error("Invalid manifest format: [app] name not found") + +def is_valid_manifest_platform(manifest, platform): + manifest_platforms = manifest["target"]["platforms"].split(",") + return platform in manifest_platforms + +def validate_manifest_platform(manifest, platform): + if not is_valid_manifest_platform(manifest, platform): + exit_with_error(f"Platform {platform} is not available in the manifest.") + +def get_manifest_target_platforms(manifest, requested_platform): + if requested_platform == "" or requested_platform is None: + return manifest["target"]["platforms"].split(",") + else: + validate_manifest_platform(manifest, requested_platform) + return [requested_platform] + +#endregion Manifest + +#region SDK download + +def sdk_download(version, platform): + sdk_root_dir = get_sdk_root_dir(version, platform) + os.makedirs(sdk_root_dir, exist_ok=True) + sdk_index_url = get_sdk_url(version, "index.json") + print(f"Downloading SDK version {version} for {platform}") + sdk_index_filepath = os.path.join(sdk_root_dir, "index.json") + if verbose: + print(f"Downloading {sdk_index_url} to {sdk_index_filepath}") + if not download_file(sdk_index_url, sdk_index_filepath): + # TODO: 404 check, print a more accurate error + print_error(f"Failed to download SDK version {version}. Check your internet connection and make sure this release exists.") + return False + with open(sdk_index_filepath) as sdk_index_json_file: + sdk_index_json = json.load(sdk_index_json_file) + sdk_platforms = sdk_index_json["platforms"] + if platform not in sdk_platforms: + print_error(f"Platform {platform} not found in {sdk_platforms} for version {version}") + return False + sdk_platform_file = sdk_platforms[platform] + sdk_zip_source_url = get_sdk_url(version, sdk_platform_file) + sdk_zip_target_filepath = os.path.join(sdk_root_dir, f"{version}-{platform}.zip") + if verbose: + print(f"Downloading {sdk_zip_source_url} to {sdk_zip_target_filepath}") + if not download_file(sdk_zip_source_url, sdk_zip_target_filepath): + print_error(f"Failed to download {sdk_zip_source_url} to {sdk_zip_target_filepath}") + return False + with zipfile.ZipFile(sdk_zip_target_filepath, "r") as zip_ref: + zip_ref.extractall(os.path.join(sdk_root_dir, "TactilitySDK")) + return True + +def sdk_download_all(version, platforms): + for platform in platforms: + if not sdk_exists(version, platform): + if not sdk_download(version, platform): + return False + else: + if verbose: + print(f"Using cached download for SDK version {version} and platform {platform}") + return True + +#endregion SDK download + +#region Building + +def get_cmake_path(platform): + return os.path.join("build", f"cmake-build-{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) + return None + +def build_all(version, platforms, skip_build): + for platform in platforms: + # 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: + if not build_first(version, platform, skip_build): + return False + else: + if not build_consecutively(version, platform, skip_build): + return False + return True + +def wait_for_process(process): + buffer = [] + if sys.platform != "win32": + os.set_blocking(process.stdout.fileno(), False) + while process.poll() is None: + while True: + line = process.stdout.readline() + if line: + decoded_line = line.decode("UTF-8") + if decoded_line != "": + buffer.append(decoded_line) + else: + break + else: + break + # Read any remaining output + for line in process.stdout: + decoded_line = line.decode("UTF-8") + if decoded_line: + buffer.append(decoded_line) + return buffer + +# The first build must call "idf.py build" and consecutive builds must call "idf.py elf" as it finishes faster. +# The problem is that the "idf.py build" always results in an error, even though the elf file is created. +# The solution is to suppress the error if we find that the elf file was created. +def build_first(version, platform, skip_build): + sdk_dir = get_sdk_dir(version, platform) + if verbose: + 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") + 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 + if elf_path is not None: + os.remove(elf_path) + if skip_build: + return True + print(f"Building first {platform} build") + cmake_path = get_cmake_path(platform) + print_status_busy(f"Building {platform} ELF") + shell_needed = sys.platform == "win32" + build_command = ["idf.py", "-B", cmake_path, "build"] + if verbose: + print(f"Running command: {" ".join(build_command)}") + with subprocess.Popen(build_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: + build_output = wait_for_process(process) + # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case + if process.returncode == 0: + print(f"{shell_color_green}Building for {platform} ✅{shell_color_reset}") + return True + else: + if find_elf_file(platform) is None: + for line in build_output: + print(line, end="") + print_status_error(f"Building {platform} ELF") + return False + else: + print_status_success(f"Building {platform} ELF") + return True + +def build_consecutively(version, platform, skip_build): + sdk_dir = get_sdk_dir(version, platform) + if verbose: + 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 skip_build: + return True + cmake_path = get_cmake_path(platform) + print_status_busy(f"Building {platform} ELF") + shell_needed = sys.platform == "win32" + build_command = ["idf.py", "-B", cmake_path, "elf"] + if verbose: + print(f"Running command: {" ".join(build_command)}") + with subprocess.Popen(build_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: + build_output = wait_for_process(process) + if process.returncode == 0: + print_status_success(f"Building {platform} ELF") + return True + else: + for line in build_output: + print(line, end="") + print_status_error(f"Building {platform} ELF") + return False + +#endregion Building + +#region Packaging + +def package_intermediate_manifest(target_path): + if not os.path.isfile("manifest.properties"): + print_error("manifest.properties not found") + return + shutil.copy("manifest.properties", os.path.join(target_path, "manifest.properties")) + +def package_intermediate_binaries(target_path, platforms): + elf_dir = os.path.join(target_path, "elf") + os.makedirs(elf_dir, exist_ok=True) + for platform in platforms: + elf_path = find_elf_file(platform) + if elf_path is None: + print_error(f"ELF file not found at {elf_path}") + return + shutil.copy(elf_path, os.path.join(elf_dir, f"{platform}.elf")) + +def package_intermediate_assets(target_path): + if os.path.isdir("assets"): + shutil.copytree("assets", os.path.join(target_path, "assets"), dirs_exist_ok=True) + +def package_intermediate(platforms): + target_path = os.path.join("build", "package-intermediate") + if os.path.isdir(target_path): + shutil.rmtree(target_path) + os.makedirs(target_path, exist_ok=True) + package_intermediate_manifest(target_path) + package_intermediate_binaries(target_path, platforms) + package_intermediate_assets(target_path) + +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_all(platforms): + status = f"Building package with {platforms}" + print_status_busy(status) + package_intermediate(platforms) + # Create build/something.app + try: + tar_path = package_name(platforms) + tar = tarfile.open(tar_path, mode="w", format=tarfile.USTAR_FORMAT) + tar.add(os.path.join("build", "package-intermediate"), arcname="") + tar.close() + print_status_success(status) + return True + except Exception as e: + print_status_error(f"Building package failed: {e}") + return False + +#endregion Packaging + +def setup_environment(): + global ttbuild_path + os.makedirs(ttbuild_path, exist_ok=True) + +def build_action(manifest, platform_arg): + # Environment validation + validate_environment() + platforms_to_build = get_manifest_target_platforms(manifest, platform_arg) + + if use_local_sdk: + global local_base_path + local_base_path = os.environ.get("TACTILITY_SDK_PATH") + validate_local_sdks(platforms_to_build, manifest["target"]["sdk"]) + + if should_fetch_sdkconfig_files(platforms_to_build): + fetch_sdkconfig_files(platforms_to_build) + + if not use_local_sdk: + sdk_json = read_sdk_json() + validate_self(sdk_json) + # Build + sdk_version = manifest["target"]["sdk"] + if not use_local_sdk: + if not sdk_download_all(sdk_version, platforms_to_build): + exit_with_error("Failed to download one or more SDKs") + if not build_all(sdk_version, platforms_to_build, skip_build): # Environment validation + return False + if not skip_build: + package_all(platforms_to_build) + return True + +def clean_action(): + if os.path.exists("build"): + print_status_busy("Removing build/") + shutil.rmtree("build") + print_status_success("Removed build/") + else: + print("Nothing to clean") + +def clear_cache_action(): + if os.path.exists(ttbuild_path): + print_status_busy(f"Removing {ttbuild_path}/") + shutil.rmtree(ttbuild_path) + print_status_success(f"Removed {ttbuild_path}/") + else: + print("Nothing to clear") + +def update_self_action(): + sdk_json = read_sdk_json() + tool_download_url = sdk_json["toolDownloadUrl"] + if download_file(tool_download_url, "tactility.py"): + print("Updated") + else: + exit_with_error("Update failed") + +def get_device_info(ip): + print_status_busy(f"Requesting device info") + url = get_url(ip, "/info") + try: + response = requests.get(url) + if response.status_code != 200: + print_error("Run failed") + else: + print_status_success(f"Received device info:") + print(response.json()) + except requests.RequestException as e: + print_status_error(f"Device info request failed: {e}") + +def run_action(manifest, ip): + app_id = manifest["app"]["id"] + print_status_busy("Running") + url = get_url(ip, "/app/run") + params = {'id': app_id} + try: + response = requests.post(url, params=params) + if response.status_code != 200: + print_error("Run failed") + else: + print_status_success("Running") + except requests.RequestException as e: + print_status_error(f"Running request failed: {e}") + +def install_action(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) + # print(f"Installing {package_path} to {ip}") + url = get_url(ip, "/app/install") + try: + # Prepare multipart form data + with open(package_path, 'rb') as file: + files = { + 'elf': file + } + response = requests.put(url, files=files) + if response.status_code != 200: + print_status_error("Install failed") + return False + else: + print_status_success("Installing") + return True + except requests.RequestException as e: + print_status_error(f"Install request failed: {e}") + return False + except IOError as e: + print_status_error(f"Install file error: {e}") + return False + +def uninstall_action(manifest, ip): + app_id = manifest["app"]["id"] + print_status_busy("Uninstalling") + url = get_url(ip, "/app/uninstall") + params = {'id': app_id} + try: + response = requests.put(url, params=params) + if response.status_code != 200: + print_status_error("Server responded that uninstall failed") + else: + print_status_success("Uninstalled") + except requests.RequestException as e: + print_status_error(f"Uninstall request failed: {e}") + +#region Main + +if __name__ == "__main__": + print(f"Tactility Build System v{ttbuild_version}") + if "--help" in sys.argv: + print_help() + sys.exit() + # Argument validation + if len(sys.argv) == 1: + print_help() + sys.exit(1) + if "--verbose" in sys.argv: + verbose = True + sys.argv.remove("--verbose") + skip_build = False + if "--skip-build" in sys.argv: + skip_build = True + sys.argv.remove("--skip-build") + if "--local-sdk" in sys.argv: + use_local_sdk = True + sys.argv.remove("--local-sdk") + action_arg = sys.argv[1] + + # Environment setup + setup_environment() + if not os.path.isfile("manifest.properties"): + exit_with_error("manifest.properties not found") + manifest = read_manifest() + validate_manifest(manifest) + all_platform_targets = manifest["target"]["platforms"].split(",") + # Update SDK cache (tool.json) + if not use_local_sdk and should_update_tool_json() and not update_tool_json(): + exit_with_error("Failed to retrieve SDK info") + # Actions + if action_arg == "build": + if len(sys.argv) < 2: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + if len(sys.argv) > 2: + platform = sys.argv[2] + if not build_action(manifest, platform): + sys.exit(1) + elif action_arg == "clean": + clean_action() + elif action_arg == "clearcache": + clear_cache_action() + elif action_arg == "updateself": + update_self_action() + elif action_arg == "run": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + run_action(manifest, sys.argv[2]) + elif action_arg == "install": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + platforms_to_install = all_platform_targets + if len(sys.argv) >= 4: + platform = sys.argv[3] + platforms_to_install = [platform] + install_action(sys.argv[2], platforms_to_install) + elif action_arg == "uninstall": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + uninstall_action(manifest, sys.argv[2]) + elif action_arg == "bir" or action_arg == "brrr": + if len(sys.argv) < 3: + print_help() + exit_with_error("Commandline parameter missing") + platform = None + platforms_to_install = all_platform_targets + if len(sys.argv) >= 4: + platform = sys.argv[3] + platforms_to_install = [platform] + if build_action(manifest, platform): + if install_action(sys.argv[2], platforms_to_install): + run_action(manifest, sys.argv[2]) + else: + print_help() + exit_with_error("Unknown commandline parameter") + +#endregion Main From 4d1ef5d2d4ccd64c6af69a7262bed9acc832df2d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:01:24 +0100 Subject: [PATCH 03/11] Fix command --- .github/actions/build-sdk/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index f5d192268..3800fe31c 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -37,7 +37,7 @@ runs: # NOTE: Update with ESP-IDF! ESP_IDF_VERSION: '5.5' TACTILITY_SDK_PATH: '../../release/TactilitySDK' - run: python.py tactility.py build ${{ inputs.arch }} --local-sdk + run: python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: From 9ea50147126bdfb47c0fac6c8af2701468db05f4 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:13:26 +0100 Subject: [PATCH 04/11] Fixes --- .github/actions/build-sdk/action.yml | 2 +- Tests/HelloWorld/tactility.py | 50 +++++++++++++++++++--------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 3800fe31c..f8a857a08 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -31,7 +31,7 @@ runs: ESP_IDF_VERSION: '5.5' run: python Buildscripts/release-sdk.py release/TactilitySDK - name: 'Test Integration' - shell: bash + uses: espressif/esp-idf-ci-action@v1 working-directory: Tests/HelloWorld env: # NOTE: Update with ESP-IDF! diff --git a/Tests/HelloWorld/tactility.py b/Tests/HelloWorld/tactility.py index d27bef4bf..84ba1b253 100644 --- a/Tests/HelloWorld/tactility.py +++ b/Tests/HelloWorld/tactility.py @@ -10,15 +10,17 @@ import zipfile import requests import tarfile +from urllib.parse import urlparse ttbuild_path = ".tactility" -ttbuild_version = "3.2.0" +ttbuild_version = "3.3.0" ttbuild_cdn = "https://cdn.tactilityproject.org" ttbuild_sdk_json_validity = 3600 # seconds ttport = 6666 verbose = False use_local_sdk = False local_base_path = None +http_timeout_seconds = 10 shell_color_red = "\033[91m" shell_color_orange = "\033[93m" @@ -55,6 +57,10 @@ def download_file(url, filepath): global verbose if verbose: print(f"Downloading from {url} to {filepath}") + parsed = urlparse(url) + if parsed.scheme not in ("http", "https"): + print_error(f"Unsupported URL scheme: {parsed.scheme}") + return False request = urllib.request.Request( url, data=None, @@ -63,10 +69,8 @@ def download_file(url, filepath): } ) try: - response = urllib.request.urlopen(request) - file = open(filepath, mode="wb") - file.write(response.read()) - file.close() + with urllib.request.urlopen(request, timeout=30) as response, open(filepath, mode="wb") as file: + file.write(response.read()) return True except OSError as error: if verbose: @@ -266,6 +270,14 @@ def get_manifest_target_platforms(manifest, requested_platform): #region SDK download +def safe_extract_zip(zip_ref, target_dir): + target_dir = os.path.realpath(target_dir) + for member in zip_ref.infolist(): + dest = os.path.realpath(os.path.join(target_dir, member.filename)) + if not dest.startswith(target_dir + os.sep): + raise ValueError(f"Invalid zip entry: {member.filename}") + zip_ref.extractall(target_dir) + def sdk_download(version, platform): sdk_root_dir = get_sdk_root_dir(version, platform) os.makedirs(sdk_root_dir, exist_ok=True) @@ -293,7 +305,7 @@ def sdk_download(version, platform): print_error(f"Failed to download {sdk_zip_source_url} to {sdk_zip_target_filepath}") return False with zipfile.ZipFile(sdk_zip_target_filepath, "r") as zip_ref: - zip_ref.extractall(os.path.join(sdk_root_dir, "TactilitySDK")) + safe_extract_zip(zip_ref, os.path.join(sdk_root_dir, "TactilitySDK")) return True def sdk_download_all(version, platforms): @@ -429,8 +441,9 @@ def build_consecutively(version, platform, skip_build): def package_intermediate_manifest(target_path): if not os.path.isfile("manifest.properties"): print_error("manifest.properties not found") - return + return False shutil.copy("manifest.properties", os.path.join(target_path, "manifest.properties")) + return True def package_intermediate_binaries(target_path, platforms): elf_dir = os.path.join(target_path, "elf") @@ -439,8 +452,9 @@ def package_intermediate_binaries(target_path, platforms): elf_path = find_elf_file(platform) if elf_path is None: print_error(f"ELF file not found at {elf_path}") - return + return False shutil.copy(elf_path, os.path.join(elf_dir, f"{platform}.elf")) + return True def package_intermediate_assets(target_path): if os.path.isdir("assets"): @@ -451,20 +465,24 @@ def package_intermediate(platforms): if os.path.isdir(target_path): shutil.rmtree(target_path) os.makedirs(target_path, exist_ok=True) - package_intermediate_manifest(target_path) - package_intermediate_binaries(target_path, platforms) + if not package_intermediate_manifest(target_path): + return False + if not package_intermediate_binaries(target_path, platforms): + return False 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_all(platforms): status = f"Building package with {platforms}" print_status_busy(status) - package_intermediate(platforms) + if not package_intermediate(platforms): + print_status_error("Building package failed: missing inputs") + return False # Create build/something.app try: tar_path = package_name(platforms) @@ -538,7 +556,7 @@ def get_device_info(ip): print_status_busy(f"Requesting device info") url = get_url(ip, "/info") try: - response = requests.get(url) + response = requests.get(url, timeout=http_timeout_seconds) if response.status_code != 200: print_error("Run failed") else: @@ -553,7 +571,7 @@ def run_action(manifest, ip): url = get_url(ip, "/app/run") params = {'id': app_id} try: - response = requests.post(url, params=params) + response = requests.post(url, params=params, timeout=http_timeout_seconds) if response.status_code != 200: print_error("Run failed") else: @@ -577,7 +595,7 @@ def install_action(ip, platforms): files = { 'elf': file } - response = requests.put(url, files=files) + response = requests.put(url, files=files, timeout=http_timeout_seconds) if response.status_code != 200: print_status_error("Install failed") return False @@ -597,7 +615,7 @@ def uninstall_action(manifest, ip): url = get_url(ip, "/app/uninstall") params = {'id': app_id} try: - response = requests.put(url, params=params) + response = requests.put(url, params=params, timeout=http_timeout_seconds) if response.status_code != 200: print_status_error("Server responded that uninstall failed") else: From c4405f6d03a4e76ba54836403524c5c94a354b88 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:20:16 +0100 Subject: [PATCH 05/11] Fix --- .github/actions/build-sdk/action.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index f8a857a08..18726b119 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -32,12 +32,13 @@ runs: run: python Buildscripts/release-sdk.py release/TactilitySDK - name: 'Test Integration' uses: espressif/esp-idf-ci-action@v1 - working-directory: Tests/HelloWorld env: - # NOTE: Update with ESP-IDF! - ESP_IDF_VERSION: '5.5' TACTILITY_SDK_PATH: '../../release/TactilitySDK' - run: python tactility.py build ${{ inputs.arch }} --local-sdk + with: + esp_idf_version: v5.5 + target: ${{ inputs.arch }} + path: 'Tests/HelloWorld/' + command: python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: From ad2ed017464dac8fcacddcdd178f6e52af4c10c0 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:30:03 +0100 Subject: [PATCH 06/11] Fixes --- .github/actions/build-sdk/action.yml | 4 +--- Tests/HelloWorld/tactility.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 18726b119..06d16dc0b 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -32,13 +32,11 @@ runs: run: python Buildscripts/release-sdk.py release/TactilitySDK - name: 'Test Integration' uses: espressif/esp-idf-ci-action@v1 - env: - TACTILITY_SDK_PATH: '../../release/TactilitySDK' with: esp_idf_version: v5.5 target: ${{ inputs.arch }} path: 'Tests/HelloWorld/' - command: python tactility.py build ${{ inputs.arch }} --local-sdk + command: export TACTILITY_SDK_PATH=../../release/TactilitySDK' && python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/Tests/HelloWorld/tactility.py b/Tests/HelloWorld/tactility.py index 84ba1b253..fd421ea0f 100644 --- a/Tests/HelloWorld/tactility.py +++ b/Tests/HelloWorld/tactility.py @@ -33,17 +33,17 @@ def print_help(): print("Usage: python tactility.py [action] [options]") print("") print("Actions:") - print(" build [esp32,esp32s3] Build the app. Optionally specify a platform.") - print(" esp32: ESP32") - print(" esp32s3: ESP32 S3") + print(" build [platform] Build the app. Optionally specify a platform.") + print(" Supported platforms are lower case. Example: esp32s3") + print(" Supported platforms are read from manifest.properties") print(" clean Clean the build folders") print(" clearcache Clear the SDK cache") print(" updateself Update this tool") print(" run [ip] Run the application") print(" install [ip] Install the application") print(" uninstall [ip] Uninstall the application") - print(" bir [ip] [esp32,esp32s3] Build, install then run. Optionally specify a platform.") - print(" brrr [ip] [esp32,esp32s3] Functionally the same as \"bir\", but \"app goes brrr\" meme variant.") + print(" bir [ip] [platform] Build, install then run. Optionally specify a platform.") + print(" brrr [ip] [platform] Functionally the same as \"bir\", but \"app goes brrr\" meme variant.") print("") print("Options:") print(" --help Show this commandline info") @@ -391,7 +391,7 @@ def build_first(version, platform, skip_build): shell_needed = sys.platform == "win32" build_command = ["idf.py", "-B", cmake_path, "build"] if verbose: - print(f"Running command: {" ".join(build_command)}") + print(f"Running command: {' '.join(build_command)}") with subprocess.Popen(build_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell_needed) as process: build_output = wait_for_process(process) # The return code is never expected to be 0 due to a bug in the elf cmake script, but we keep it just in case @@ -525,7 +525,8 @@ def build_action(manifest, platform_arg): if not build_all(sdk_version, platforms_to_build, skip_build): # Environment validation return False if not skip_build: - package_all(platforms_to_build) + if not package_all(platforms_to_build): + return False return True def clean_action(): From 942a3ee8f1107d068a9301fe26db665e0718306d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:40:01 +0100 Subject: [PATCH 07/11] Fix --- .github/actions/build-sdk/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 06d16dc0b..5b6429f5b 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -36,7 +36,7 @@ runs: esp_idf_version: v5.5 target: ${{ inputs.arch }} path: 'Tests/HelloWorld/' - command: export TACTILITY_SDK_PATH=../../release/TactilitySDK' && python tactility.py build ${{ inputs.arch }} --local-sdk + command: export TACTILITY_SDK_PATH=../../release/TactilitySDK && python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: From efd3c7fc02d421ccb92fcf2d5a4c7c15d9893e3f Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 21:52:48 +0100 Subject: [PATCH 08/11] Fixes --- .github/actions/build-sdk/action.yml | 3 +-- Tests/HelloWorld/tactility.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 5b6429f5b..3cd4f2958 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -35,8 +35,7 @@ runs: with: esp_idf_version: v5.5 target: ${{ inputs.arch }} - path: 'Tests/HelloWorld/' - command: export TACTILITY_SDK_PATH=../../release/TactilitySDK && python tactility.py build ${{ inputs.arch }} --local-sdk + command: export TACTILITY_SDK_PATH=../../release/TactilitySDK && cd Tests/HelloWorld && python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/Tests/HelloWorld/tactility.py b/Tests/HelloWorld/tactility.py index fd421ea0f..d64e1008e 100644 --- a/Tests/HelloWorld/tactility.py +++ b/Tests/HelloWorld/tactility.py @@ -501,7 +501,7 @@ def setup_environment(): global ttbuild_path os.makedirs(ttbuild_path, exist_ok=True) -def build_action(manifest, platform_arg): +def build_action(manifest, platform_arg, skip_build): # Environment validation validate_environment() platforms_to_build = get_manifest_target_platforms(manifest, platform_arg) @@ -665,7 +665,7 @@ def uninstall_action(manifest, ip): platform = None if len(sys.argv) > 2: platform = sys.argv[2] - if not build_action(manifest, platform): + if not build_action(manifest, platform, skip_build): sys.exit(1) elif action_arg == "clean": clean_action() @@ -702,7 +702,7 @@ def uninstall_action(manifest, ip): if len(sys.argv) >= 4: platform = sys.argv[3] platforms_to_install = [platform] - if build_action(manifest, platform): + if build_action(manifest, platform, skip_build): if install_action(sys.argv[2], platforms_to_install): run_action(manifest, sys.argv[2]) else: From d35b3c35816bd64ad1a4a027d95eb29d6e355320 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 22:11:01 +0100 Subject: [PATCH 09/11] Fix --- .github/actions/build-sdk/action.yml | 11 ++++++++++- .github/workflows/build.yml | 6 +++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index 3cd4f2958..da336b1b0 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -30,12 +30,21 @@ runs: # NOTE: Update with ESP-IDF! ESP_IDF_VERSION: '5.5' run: python Buildscripts/release-sdk.py release/TactilitySDK + - name: 'Test Integration Prep' + shell: bash + run: | + ls + ls release/TactilitySDK + TACTILITY_VERSION=`cat version.txt` + TACTILITY_SDK_NAME="$TACTILITY_VERSION-${{ inputs.arch }}" + mkdir -p test_sdk/$TACTILITY_SDK_NAME + cp -r release/TactilitySDK test_sdk/$TACTILITY_SDK_NAME - name: 'Test Integration' uses: espressif/esp-idf-ci-action@v1 with: esp_idf_version: v5.5 target: ${{ inputs.arch }} - command: export TACTILITY_SDK_PATH=../../release/TactilitySDK && cd Tests/HelloWorld && python tactility.py build ${{ inputs.arch }} --local-sdk + command: export TACTILITY_SDK_PATH=../../test_sdk && cd Tests/HelloWorld && python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13328effc..3eff4eaaf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,9 +16,9 @@ jobs: matrix: board: [ { id: generic-esp32, arch: esp32 }, - { id: generic-esp32c6, arch: esp32c6 }, - { id: generic-esp32p4, arch: esp32p4 }, - { id: generic-esp32s3, arch: esp32s3 }, +# { id: generic-esp32c6, arch: esp32c6 }, +# { id: generic-esp32p4, arch: esp32p4 }, +# { id: generic-esp32s3, arch: esp32s3 }, ] runs-on: ubuntu-latest steps: From c98cf149e802faf7cc4333de6e8b2d30b07b92e1 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 22:29:24 +0100 Subject: [PATCH 10/11] Cleanup and improvements --- .github/actions/build-sdk/action.yml | 5 +-- .github/workflows/build.yml | 6 +-- Tests/HelloWorld/main/Source/main.c | 18 -------- .../CMakeLists.txt | 4 +- .../main/CMakeLists.txt | 0 Tests/SdkIntegration/main/Source/main.c | 43 +++++++++++++++++++ .../manifest.properties | 4 +- .../tactility.py | 0 8 files changed, 52 insertions(+), 28 deletions(-) delete mode 100644 Tests/HelloWorld/main/Source/main.c rename Tests/{HelloWorld => SdkIntegration}/CMakeLists.txt (90%) rename Tests/{HelloWorld => SdkIntegration}/main/CMakeLists.txt (100%) create mode 100644 Tests/SdkIntegration/main/Source/main.c rename Tests/{HelloWorld => SdkIntegration}/manifest.properties (71%) rename Tests/{HelloWorld => SdkIntegration}/tactility.py (100%) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index da336b1b0..e870ea96d 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -32,9 +32,8 @@ runs: run: python Buildscripts/release-sdk.py release/TactilitySDK - name: 'Test Integration Prep' shell: bash + # Make a copy of the SDK with the folder structure that tactility.py expects run: | - ls - ls release/TactilitySDK TACTILITY_VERSION=`cat version.txt` TACTILITY_SDK_NAME="$TACTILITY_VERSION-${{ inputs.arch }}" mkdir -p test_sdk/$TACTILITY_SDK_NAME @@ -44,7 +43,7 @@ runs: with: esp_idf_version: v5.5 target: ${{ inputs.arch }} - command: export TACTILITY_SDK_PATH=../../test_sdk && cd Tests/HelloWorld && python tactility.py build ${{ inputs.arch }} --local-sdk + command: export TACTILITY_SDK_PATH=../../test_sdk && cd Tests/SdkIntegration && python tactility.py build ${{ inputs.arch }} --local-sdk - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3eff4eaaf..13328effc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,9 +16,9 @@ jobs: matrix: board: [ { id: generic-esp32, arch: esp32 }, -# { id: generic-esp32c6, arch: esp32c6 }, -# { id: generic-esp32p4, arch: esp32p4 }, -# { id: generic-esp32s3, arch: esp32s3 }, + { id: generic-esp32c6, arch: esp32c6 }, + { id: generic-esp32p4, arch: esp32p4 }, + { id: generic-esp32s3, arch: esp32s3 }, ] runs-on: ubuntu-latest steps: diff --git a/Tests/HelloWorld/main/Source/main.c b/Tests/HelloWorld/main/Source/main.c deleted file mode 100644 index 2f7459cdd..000000000 --- a/Tests/HelloWorld/main/Source/main.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) { - lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app); - lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); - - lv_obj_t* label = lv_label_create(parent); - lv_label_set_text(label, "Hello, world!"); - lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); -} - -int main(int argc, char* argv[]) { - tt_app_register((AppRegistration) { - .onShow = onShowApp - }); - return 0; -} diff --git a/Tests/HelloWorld/CMakeLists.txt b/Tests/SdkIntegration/CMakeLists.txt similarity index 90% rename from Tests/HelloWorld/CMakeLists.txt rename to Tests/SdkIntegration/CMakeLists.txt index 95d0cb2e4..046853f6b 100644 --- a/Tests/HelloWorld/CMakeLists.txt +++ b/Tests/SdkIntegration/CMakeLists.txt @@ -12,5 +12,5 @@ endif() include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake") set(EXTRA_COMPONENT_DIRS ${TACTILITY_SDK_PATH}) -project(HelloWorld) -tactility_project(HelloWorld) +project(SdkTest) +tactility_project(SdkTest) diff --git a/Tests/HelloWorld/main/CMakeLists.txt b/Tests/SdkIntegration/main/CMakeLists.txt similarity index 100% rename from Tests/HelloWorld/main/CMakeLists.txt rename to Tests/SdkIntegration/main/CMakeLists.txt diff --git a/Tests/SdkIntegration/main/Source/main.c b/Tests/SdkIntegration/main/Source/main.c new file mode 100644 index 000000000..eb8713c80 --- /dev/null +++ b/Tests/SdkIntegration/main/Source/main.c @@ -0,0 +1,43 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) { + lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t* label = lv_label_create(parent); + lv_label_set_text(label, "Hello, world!"); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); +} + +int main(int argc, char* argv[]) { + tt_app_register((AppRegistration) { + .onShow = onShowApp + }); + return 0; +} diff --git a/Tests/HelloWorld/manifest.properties b/Tests/SdkIntegration/manifest.properties similarity index 71% rename from Tests/HelloWorld/manifest.properties rename to Tests/SdkIntegration/manifest.properties index fa385e8c7..1d0db6e47 100644 --- a/Tests/HelloWorld/manifest.properties +++ b/Tests/SdkIntegration/manifest.properties @@ -4,7 +4,7 @@ version=0.1 sdk=0.7.0-dev platforms=esp32,esp32s3,esp32c6,esp32p4 [app] -id=one.tactility.helloworldtest +id=one.tactility.sdktest versionName=0.1.0 versionCode=1 -name=Hello World +name=SDK Test diff --git a/Tests/HelloWorld/tactility.py b/Tests/SdkIntegration/tactility.py similarity index 100% rename from Tests/HelloWorld/tactility.py rename to Tests/SdkIntegration/tactility.py From 8069d034c08e494747a5b08082f68db3d2500c72 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 6 Feb 2026 22:38:21 +0100 Subject: [PATCH 11/11] Fixes --- .github/actions/build-sdk/action.yml | 6 +++--- Tests/SdkIntegration/main/Source/main.c | 2 -- Tests/SdkIntegration/manifest.properties | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/actions/build-sdk/action.yml b/.github/actions/build-sdk/action.yml index e870ea96d..36140ceaa 100644 --- a/.github/actions/build-sdk/action.yml +++ b/.github/actions/build-sdk/action.yml @@ -32,10 +32,10 @@ runs: run: python Buildscripts/release-sdk.py release/TactilitySDK - name: 'Test Integration Prep' shell: bash - # Make a copy of the SDK with the folder structure that tactility.py expects + # The manifest.properties of our integration test uses version 0.0.0 to indicate that it is not using a normal SDK + # This way, it only works with our custom build. That means we have to create a copy of the SDK with the correct folder structure: run: | - TACTILITY_VERSION=`cat version.txt` - TACTILITY_SDK_NAME="$TACTILITY_VERSION-${{ inputs.arch }}" + TACTILITY_SDK_NAME="0.0.0-${{ inputs.arch }}" mkdir -p test_sdk/$TACTILITY_SDK_NAME cp -r release/TactilitySDK test_sdk/$TACTILITY_SDK_NAME - name: 'Test Integration' diff --git a/Tests/SdkIntegration/main/Source/main.c b/Tests/SdkIntegration/main/Source/main.c index eb8713c80..13c029597 100644 --- a/Tests/SdkIntegration/main/Source/main.c +++ b/Tests/SdkIntegration/main/Source/main.c @@ -22,8 +22,6 @@ #include #include -#include - #include static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) { diff --git a/Tests/SdkIntegration/manifest.properties b/Tests/SdkIntegration/manifest.properties index 1d0db6e47..d0fdeeb9f 100644 --- a/Tests/SdkIntegration/manifest.properties +++ b/Tests/SdkIntegration/manifest.properties @@ -1,7 +1,7 @@ [manifest] version=0.1 [target] -sdk=0.7.0-dev +sdk=0.0.0 platforms=esp32,esp32s3,esp32c6,esp32p4 [app] id=one.tactility.sdktest