diff --git a/application/basic_demo/.gitignore b/application/basic_demo/.gitignore index 7401846..77a4b4e 100644 --- a/application/basic_demo/.gitignore +++ b/application/basic_demo/.gitignore @@ -1 +1,2 @@ fatfs_image/skills +fatfs_image/scripts/builtin diff --git a/application/basic_demo/CMakeLists.txt b/application/basic_demo/CMakeLists.txt index cb88c04..a8f5541 100644 --- a/application/basic_demo/CMakeLists.txt +++ b/application/basic_demo/CMakeLists.txt @@ -15,3 +15,8 @@ basic_demo_enable_component_skills_sync( DEMO_SKILLS_DIR "${CMAKE_SOURCE_DIR}/fatfs_image/skills" MANIFEST_PATH "${CMAKE_BINARY_DIR}/component_skills_manifest.json" ) +basic_demo_enable_component_builtin_lua_sync( + TARGET fatfs_storage_bin + OUTPUT_DIR "${CMAKE_SOURCE_DIR}/fatfs_image/scripts/builtin" + MANIFEST_PATH "${CMAKE_BINARY_DIR}/component_builtin_lua_manifest.json" +) diff --git a/application/basic_demo/fatfs_image/scripts/builtin/audio_play_test_wav.lua b/application/basic_demo/fatfs_image/scripts/builtin/audio_play_test_wav.lua deleted file mode 100644 index 271b315..0000000 --- a/application/basic_demo/fatfs_image/scripts/builtin/audio_play_test_wav.lua +++ /dev/null @@ -1,31 +0,0 @@ -local audio = require("audio") -local bm = require("board_manager") -local storage = require("storage") - -local TEST_WAV_PATH = storage.join_path(storage.get_root_dir(), "test.wav") - -local output_codec, output_rate, output_channels, output_bits = - bm.get_audio_codec_output_params("audio_dac") -if not output_codec then - print("[audio_play_test_wav] ERROR: get_audio_codec_output_params(audio_dac) failed: " .. tostring(output_rate)) - return -end - -local output, out_err = audio.new_output(output_codec, output_rate, output_channels, output_bits) -if not output then - print("[audio_play_test_wav] ERROR: new_output failed: " .. tostring(out_err)) - return -end - -local ok, err = xpcall(function() - print(string.format("[audio_play_test_wav] output=%dHz/%dch/%dbit", - output_rate, output_channels, output_bits)) - print("[audio_play_test_wav] playing " .. TEST_WAV_PATH .. " ...") - audio.play_wav(output, TEST_WAV_PATH) -end, debug.traceback) - -pcall(audio.close, output) -if not ok then - error(err) -end -print("[audio_play_test_wav] done") diff --git a/application/basic_demo/fatfs_image/scripts/builtin/button_play_test_wav.lua b/application/basic_demo/fatfs_image/scripts/builtin/button_play_test_wav.lua deleted file mode 100644 index d2f1a43..0000000 --- a/application/basic_demo/fatfs_image/scripts/builtin/button_play_test_wav.lua +++ /dev/null @@ -1,90 +0,0 @@ --- Button single_click plays test.wav. Requires board_manager for audio_dac codec params only. --- Optional args: pin, duration_ms, poll_ms, test_wav (filename under FATFS root, default test.wav). --- Pattern: xpcall(run) + cleanup() closes button + audio; integer args via args table. -local audio = require("audio") -local bm = require("board_manager") -local btn = require("button") -local delay = require("delay") -local storage = require("storage") - -local a = type(args) == "table" and args or {} -local function int_arg(k, default) - local v = a[k] - if type(v) == "number" then - return math.floor(v) - end - return default -end - -local BUTTON_GPIO_NUM = int_arg("pin", 28) -local RUN_TIME_MS = int_arg("duration_ms", 60000) -local POLL_INTERVAL_MS = int_arg("poll_ms", 10) -local wav_name = (type(a.test_wav) == "string" and a.test_wav ~= "") and a.test_wav or "test.wav" -local TEST_WAV_PATH = storage.join_path(storage.get_root_dir(), wav_name) - -local output -local handle -local is_playing = false - -local function cleanup() - if handle then - pcall(btn.off, handle) - pcall(btn.close, handle) - handle = nil - end - if output then - pcall(audio.close, output) - output = nil - end -end - -local function run() - local output_codec, output_rate, output_channels, output_bits = - bm.get_audio_codec_output_params("audio_dac") - if not output_codec then - error("[button_play_test_wav] get_audio_codec_output_params(audio_dac) failed: " .. tostring(output_rate)) - end - - local out_h, out_err = audio.new_output(output_codec, output_rate, output_channels, output_bits) - if not out_h then - error("[button_play_test_wav] new_output failed: " .. tostring(out_err)) - end - output = out_h - - local btn_h, herr = btn.new(BUTTON_GPIO_NUM, 0) - if not btn_h then - error("[button_play_test_wav] button.new failed: " .. tostring(herr)) - end - handle = btn_h - - btn.on(handle, "single_click", function() - if is_playing then - print("[button_play_test_wav] playback already in progress") - return - end - - is_playing = true - print("[button_play_test_wav] playing " .. TEST_WAV_PATH .. " ...") - audio.play_wav(output, TEST_WAV_PATH) - print("[button_play_test_wav] playback done") - is_playing = false - end) - - print(string.format("[button_play_test_wav] button gpio=%d output=%dHz/%dch/%dbit wav=%s", - BUTTON_GPIO_NUM, output_rate, output_channels, output_bits, wav_name)) - print("[button_play_test_wav] press the button once to play") - print("[button_play_test_wav] running for " .. tostring(RUN_TIME_MS) .. " ms") - - for _ = 1, math.max(1, math.floor(RUN_TIME_MS / POLL_INTERVAL_MS)) do - btn.dispatch() - delay.delay_ms(POLL_INTERVAL_MS) - end -end - -local ok, err = xpcall(run, debug.traceback) -cleanup() -if not ok then - error(err) -end - -print("[button_play_test_wav] done") diff --git a/application/basic_demo/fatfs_image/scripts/builtin/hello.lua b/application/basic_demo/fatfs_image/scripts/builtin/hello.lua deleted file mode 100644 index 1c98aa9..0000000 --- a/application/basic_demo/fatfs_image/scripts/builtin/hello.lua +++ /dev/null @@ -1 +0,0 @@ -print("hello lua!") diff --git a/application/basic_demo/fatfs_image/test.wav b/application/basic_demo/fatfs_image/test.wav deleted file mode 100644 index 8148f8b..0000000 Binary files a/application/basic_demo/fatfs_image/test.wav and /dev/null differ diff --git a/application/basic_demo/fatfs_image/scripts/builtin/audio_fft_2.lua b/application/basic_demo/main/lua_scripts/advance_audio_fft.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/audio_fft_2.lua rename to application/basic_demo/main/lua_scripts/advance_audio_fft.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/camera_preview_demo.lua b/application/basic_demo/main/lua_scripts/advance_camera_preview.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/camera_preview_demo.lua rename to application/basic_demo/main/lua_scripts/advance_camera_preview.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/clock_dial_demo.lua b/application/basic_demo/main/lua_scripts/advance_clock_dial_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/clock_dial_demo.lua rename to application/basic_demo/main/lua_scripts/advance_clock_dial_demo.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/flappybird.lua b/application/basic_demo/main/lua_scripts/advance_flappybird.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/flappybird.lua rename to application/basic_demo/main/lua_scripts/advance_flappybird.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/lcd_touch_paint.lua b/application/basic_demo/main/lua_scripts/advance_lcd_touch_paint.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/lcd_touch_paint.lua rename to application/basic_demo/main/lua_scripts/advance_lcd_touch_paint.lua diff --git a/application/basic_demo/tools/cmake/skills_sync.cmake b/application/basic_demo/tools/cmake/skills_sync.cmake index a7fdbda..d951a7a 100644 --- a/application/basic_demo/tools/cmake/skills_sync.cmake +++ b/application/basic_demo/tools/cmake/skills_sync.cmake @@ -1,11 +1,14 @@ -function(basic_demo_collect_skill_components out_var) +function(basic_demo_collect_project_component_dirs out_var) + set(options INCLUDE_MAIN_DIR) + cmake_parse_arguments(arg "${options}" "" "" ${ARGN}) + idf_build_get_property(build_components BUILD_COMPONENTS) get_filename_component(project_root_dir "${CMAKE_SOURCE_DIR}" REALPATH) get_filename_component(shared_components_root "${CMAKE_SOURCE_DIR}/../../components" REALPATH) get_filename_component(managed_components_root "${CMAKE_SOURCE_DIR}/managed_components" REALPATH) - set(component_args) + set(component_specs) foreach(component_name IN LISTS build_components) idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR) if(NOT component_dir) @@ -20,6 +23,85 @@ function(basic_demo_collect_skill_components out_var) if(component_dir MATCHES "^${project_root_dir}(/|$)" OR component_dir MATCHES "^${shared_components_root}(/|$)") + if(arg_INCLUDE_MAIN_DIR AND component_name STREQUAL "main") + continue() + endif() + list(APPEND component_specs "${component_name}=${component_dir}") + endif() + endforeach() + + if(arg_INCLUDE_MAIN_DIR) + get_filename_component(main_dir "${CMAKE_SOURCE_DIR}/main" REALPATH) + list(APPEND component_specs "__main__=${main_dir}") + endif() + + set(${out_var} "${component_specs}" PARENT_SCOPE) +endfunction() + +function(basic_demo_collect_component_resource_files out_var resource_subdir) + set(options INCLUDE_MAIN_DIR) + set(multi_value_args GLOB_PATTERNS) + cmake_parse_arguments(arg "${options}" "" "${multi_value_args}" ${ARGN}) + + if(NOT arg_GLOB_PATTERNS) + message(FATAL_ERROR "basic_demo_collect_component_resource_files requires GLOB_PATTERNS") + endif() + + basic_demo_collect_project_component_dirs(component_specs ${ARGN}) + + set(resource_files) + foreach(component_spec IN LISTS component_specs) + string(FIND "${component_spec}" "=" separator_index) + if(separator_index EQUAL -1) + message(FATAL_ERROR "Invalid component spec '${component_spec}'") + endif() + + math(EXPR path_index "${separator_index} + 1") + string(SUBSTRING "${component_spec}" ${path_index} -1 component_dir) + set(resource_dir "${component_dir}/${resource_subdir}") + + if(NOT EXISTS "${resource_dir}") + continue() + endif() + + foreach(glob_pattern IN LISTS arg_GLOB_PATTERNS) + file( + GLOB component_resource_files + CONFIGURE_DEPENDS + LIST_DIRECTORIES false + "${resource_dir}/${glob_pattern}" + ) + list(APPEND resource_files ${component_resource_files}) + endforeach() + endforeach() + + list(REMOVE_DUPLICATES resource_files) + set(${out_var} "${resource_files}" PARENT_SCOPE) +endfunction() + +function(basic_demo_append_component_args out_var) + set(options INCLUDE_MAIN_DIR) + set(one_value_args MAIN_DIR_ARG) + cmake_parse_arguments(arg "${options}" "${one_value_args}" "" ${ARGN}) + + basic_demo_collect_project_component_dirs(component_specs ${ARGN}) + + set(component_args) + foreach(component_spec IN LISTS component_specs) + string(FIND "${component_spec}" "=" separator_index) + if(separator_index EQUAL -1) + message(FATAL_ERROR "Invalid component spec '${component_spec}'") + endif() + + string(SUBSTRING "${component_spec}" 0 ${separator_index} component_name) + math(EXPR path_index "${separator_index} + 1") + string(SUBSTRING "${component_spec}" ${path_index} -1 component_dir) + + if(component_name STREQUAL "__main__") + if(arg_MAIN_DIR_ARG) + list(APPEND component_args "${arg_MAIN_DIR_ARG}" "${component_dir}") + endif() + else() list(APPEND component_args --component "${component_name}=${component_dir}") endif() endforeach() @@ -27,43 +109,89 @@ function(basic_demo_collect_skill_components out_var) set(${out_var} "${component_args}" PARENT_SCOPE) endfunction() -function(basic_demo_collect_skill_files out_var) - idf_build_get_property(build_components BUILD_COMPONENTS) +function(basic_demo_get_resource_generator_targets out_var resource_kind) + set(property_names "BASIC_DEMO_RESOURCE_GENERATOR_TARGETS_${resource_kind}") + if(resource_kind STREQUAL "skills") + list(APPEND property_names "BASIC_DEMO_SKILL_GENERATOR_TARGETS") + endif() - get_filename_component(project_root_dir "${CMAKE_SOURCE_DIR}" REALPATH) - get_filename_component(shared_components_root "${CMAKE_SOURCE_DIR}/../../components" REALPATH) - get_filename_component(managed_components_root "${CMAKE_SOURCE_DIR}/managed_components" REALPATH) - - set(skill_files) - foreach(component_name IN LISTS build_components) - idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR) - if(NOT component_dir) - continue() + set(generator_targets) + foreach(property_name IN LISTS property_names) + get_property(property_set GLOBAL PROPERTY "${property_name}" SET) + if(property_set) + get_property(property_targets GLOBAL PROPERTY "${property_name}") + if(property_targets) + list(APPEND generator_targets ${property_targets}) + endif() endif() - - get_filename_component(component_dir "${component_dir}" REALPATH) - - if(component_dir MATCHES "^${managed_components_root}(/|$)") - continue() - endif() - - if(NOT (component_dir MATCHES "^${project_root_dir}(/|$)" OR - component_dir MATCHES "^${shared_components_root}(/|$)")) - continue() - endif() - - if(NOT EXISTS "${component_dir}/skills") - continue() - endif() - - file(GLOB component_skill_files CONFIGURE_DEPENDS LIST_DIRECTORIES false "${component_dir}/skills/*") - list(APPEND skill_files ${component_skill_files}) endforeach() - list(REMOVE_DUPLICATES skill_files) - set(${out_var} "${skill_files}" PARENT_SCOPE) + list(REMOVE_DUPLICATES generator_targets) + set(${out_var} "${generator_targets}" PARENT_SCOPE) endfunction() +function(basic_demo_enable_component_resource_sync) + set(options INCLUDE_MAIN_DIR) + set(one_value_args + TARGET + RESOURCE_KIND + RESOURCE_SUBDIR + OUTPUT_DIR + OUTPUT_ARG_NAME + MANIFEST_PATH + PYTHON_SCRIPT + STAMP_PATH + MAIN_DIR_ARG + COMMENT + ) + set(multi_value_args GLOB_PATTERNS SCRIPT_ARGS) + cmake_parse_arguments(arg "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + foreach(required_arg TARGET RESOURCE_KIND RESOURCE_SUBDIR OUTPUT_DIR OUTPUT_ARG_NAME MANIFEST_PATH PYTHON_SCRIPT STAMP_PATH COMMENT) + if(NOT arg_${required_arg}) + message(FATAL_ERROR "basic_demo_enable_component_resource_sync requires ${required_arg}") + endif() + endforeach() + + if(NOT arg_GLOB_PATTERNS) + message(FATAL_ERROR "basic_demo_enable_component_resource_sync requires GLOB_PATTERNS") + endif() + + idf_build_get_property(python PYTHON) + basic_demo_collect_component_resource_files( + resource_dependency_files + "${arg_RESOURCE_SUBDIR}" + ${ARGN} + ) + basic_demo_append_component_args(component_args ${ARGN}) + + add_custom_command( + OUTPUT "${arg_STAMP_PATH}" + COMMAND ${python} + "${arg_PYTHON_SCRIPT}" + --manifest-path "${arg_MANIFEST_PATH}" + "${arg_OUTPUT_ARG_NAME}" "${arg_OUTPUT_DIR}" + ${arg_SCRIPT_ARGS} + ${component_args} + COMMAND ${CMAKE_COMMAND} -E touch "${arg_STAMP_PATH}" + DEPENDS + "${arg_PYTHON_SCRIPT}" + ${resource_dependency_files} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "${arg_COMMENT}" + VERBATIM + ) + + set(sync_target "basic_demo_sync_${arg_RESOURCE_KIND}") + add_custom_target(${sync_target} ALL DEPENDS "${arg_STAMP_PATH}") + + basic_demo_get_resource_generator_targets(generator_targets "${arg_RESOURCE_KIND}") + if(generator_targets) + add_dependencies(${sync_target} ${generator_targets}) + endif() + + add_dependencies(${arg_TARGET} ${sync_target}) +endfunction() function(basic_demo_enable_component_skills_sync) set(options) @@ -80,39 +208,53 @@ function(basic_demo_enable_component_skills_sync) message(FATAL_ERROR "basic_demo_enable_component_skills_sync requires MANIFEST_PATH") endif() - idf_build_get_property(python PYTHON) - basic_demo_collect_skill_components(skill_component_args) - basic_demo_collect_skill_files(skill_dependency_files) - set(skill_sync_extra_args) - set(skill_sync_stamp "${CMAKE_BINARY_DIR}/component_skills_sync.stamp") - + set(skill_sync_extra_args --demo-skills-dir "${arg_DEMO_SKILLS_DIR}") if(CONFIG_BASIC_DEMO_MEMORY_MODE_LIGHTWEIGHT) list(APPEND skill_sync_extra_args --exclude-skill-id memory_ops) endif() - add_custom_command( - OUTPUT "${skill_sync_stamp}" - COMMAND ${python} - "${CMAKE_SOURCE_DIR}/tools/sync_component_skills.py" - --demo-skills-dir "${arg_DEMO_SKILLS_DIR}" - --manifest-path "${arg_MANIFEST_PATH}" - ${skill_sync_extra_args} - ${skill_component_args} - COMMAND ${CMAKE_COMMAND} -E touch "${skill_sync_stamp}" - DEPENDS - "${CMAKE_SOURCE_DIR}/tools/sync_component_skills.py" - ${skill_dependency_files} - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + basic_demo_enable_component_resource_sync( + TARGET "${arg_TARGET}" + RESOURCE_KIND skills + RESOURCE_SUBDIR skills + OUTPUT_DIR "${arg_DEMO_SKILLS_DIR}" + OUTPUT_ARG_NAME --demo-skills-dir + MANIFEST_PATH "${arg_MANIFEST_PATH}" + PYTHON_SCRIPT "${CMAKE_SOURCE_DIR}/tools/sync_component_skills.py" + STAMP_PATH "${CMAKE_BINARY_DIR}/component_skills_sync.stamp" COMMENT "Sync component skills into fatfs_image/skills" - VERBATIM + GLOB_PATTERNS "*" + SCRIPT_ARGS ${skill_sync_extra_args} ) +endfunction() - add_custom_target(basic_demo_sync_skills ALL DEPENDS "${skill_sync_stamp}") +function(basic_demo_enable_component_builtin_lua_sync) + set(options) + set(one_value_args TARGET OUTPUT_DIR MANIFEST_PATH) + cmake_parse_arguments(arg "${options}" "${one_value_args}" "" ${ARGN}) - get_property(skill_generator_targets GLOBAL PROPERTY BASIC_DEMO_SKILL_GENERATOR_TARGETS) - if(skill_generator_targets) - add_dependencies(basic_demo_sync_skills ${skill_generator_targets}) + if(NOT arg_TARGET) + message(FATAL_ERROR "basic_demo_enable_component_builtin_lua_sync requires TARGET") + endif() + if(NOT arg_OUTPUT_DIR) + message(FATAL_ERROR "basic_demo_enable_component_builtin_lua_sync requires OUTPUT_DIR") + endif() + if(NOT arg_MANIFEST_PATH) + message(FATAL_ERROR "basic_demo_enable_component_builtin_lua_sync requires MANIFEST_PATH") endif() - add_dependencies(${arg_TARGET} basic_demo_sync_skills) + basic_demo_enable_component_resource_sync( + TARGET "${arg_TARGET}" + RESOURCE_KIND builtin_lua + RESOURCE_SUBDIR lua_scripts + OUTPUT_DIR "${arg_OUTPUT_DIR}" + OUTPUT_ARG_NAME --output-dir + MANIFEST_PATH "${arg_MANIFEST_PATH}" + PYTHON_SCRIPT "${CMAKE_SOURCE_DIR}/tools/sync_component_lua_scripts.py" + STAMP_PATH "${CMAKE_BINARY_DIR}/component_builtin_lua_sync.stamp" + MAIN_DIR_ARG --main-dir + INCLUDE_MAIN_DIR + COMMENT "Sync component builtin Lua scripts into fatfs_image/scripts/builtin" + GLOB_PATTERNS "*.lua" + ) endfunction() diff --git a/application/basic_demo/tools/sync_component_lua_scripts.py b/application/basic_demo/tools/sync_component_lua_scripts.py new file mode 100755 index 0000000..ee70ce8 --- /dev/null +++ b/application/basic_demo/tools/sync_component_lua_scripts.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import argparse +import json +import shutil +import sys +from pathlib import Path + + +class LuaScriptSyncError(RuntimeError): + pass + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description='Sync component Lua scripts into the basic_demo FATFS image.' + ) + parser.add_argument('--output-dir', required=True) + parser.add_argument('--manifest-path', required=True) + parser.add_argument( + '--component', + action='append', + default=[], + help='Component mapping in the form =.', + ) + parser.add_argument( + '--main-dir', + help='Optional basic_demo main directory whose lua_scripts should be treated as builtin scripts.', + ) + return parser.parse_args() + + +def fail(message: str) -> None: + raise LuaScriptSyncError(message) + + +def load_json_file(path: Path) -> object: + try: + return json.loads(path.read_text(encoding='utf-8')) + except FileNotFoundError: + fail(f'Missing JSON file: {path}') + except json.JSONDecodeError as exc: + fail(f'Invalid JSON in {path}: {exc}') + + +def load_manifest(path: Path) -> dict: + if not path.exists(): + return {'synced_files': [], 'sources': {}} + + data = load_json_file(path) + if not isinstance(data, dict): + fail(f'Manifest must be a JSON object: {path}') + + synced_files = data.get('synced_files', []) + sources = data.get('sources', {}) + if not isinstance(synced_files, list): + fail(f"Manifest field 'synced_files' must be a JSON array: {path}") + if not isinstance(sources, dict): + fail(f"Manifest field 'sources' must be a JSON object: {path}") + + return { + 'synced_files': [str(item) for item in synced_files], + 'sources': {str(key): str(value) for key, value in sources.items()}, + } + + +def parse_component_specs(raw_specs: list[str]) -> list[tuple[str, Path]]: + components: list[tuple[str, Path]] = [] + + for spec in raw_specs: + if '=' not in spec: + fail(f"Invalid component mapping '{spec}', expected =.") + name, raw_dir = spec.split('=', 1) + if not name or not raw_dir: + fail(f"Invalid component mapping '{spec}', expected =.") + components.append((name, Path(raw_dir).resolve())) + + components.sort(key=lambda item: item[0]) + return components + + +def collect_builtin_lua_scripts( + components: list[tuple[str, Path]], + main_dir: Path | None, +) -> tuple[dict[str, Path], dict[str, str]]: + copy_map: dict[str, Path] = {} + source_map: dict[str, str] = {} + + source_specs = list(components) + if main_dir is not None: + source_specs.append(('main', main_dir.resolve())) + + for source_name, source_root in source_specs: + scripts_dir = source_root / 'lua_scripts' + if not scripts_dir.is_dir(): + continue + + for script_path in sorted(scripts_dir.glob('*.lua')): + if not script_path.is_file(): + continue + + script_name = script_path.name + previous_source = source_map.get(script_name) + if previous_source: + fail( + f"Duplicate builtin Lua script '{script_name}' between {previous_source} " + f"and {source_name} ({script_path})" + ) + + resolved_path = script_path.resolve() + copy_map[script_name] = resolved_path + source_map[script_name] = f'{source_name} ({resolved_path})' + + return copy_map, source_map + + +def sync_builtin_lua_scripts( + output_dir: Path, + manifest_path: Path, + manifest: dict, + copy_map: dict[str, Path], + source_map: dict[str, str], +) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + + for old_file in manifest.get('synced_files', []): + if old_file not in copy_map: + stale_path = output_dir / old_file + if stale_path.exists(): + stale_path.unlink() + + for filename, source_path in copy_map.items(): + target_path = output_dir / filename + shutil.copy2(source_path, target_path) + + manifest_data = { + 'synced_files': sorted(copy_map.keys()), + 'sources': {filename: source_map[filename] for filename in sorted(source_map)}, + } + manifest_path.write_text( + json.dumps(manifest_data, ensure_ascii=False, indent=2) + '\n', + encoding='utf-8', + ) + + +def main() -> int: + args = parse_args() + + output_dir = Path(args.output_dir).resolve() + manifest_path = Path(args.manifest_path).resolve() + manifest_path.parent.mkdir(parents=True, exist_ok=True) + manifest = load_manifest(manifest_path) + + components = parse_component_specs(args.component) + main_dir = Path(args.main_dir).resolve() if args.main_dir else None + copy_map, source_map = collect_builtin_lua_scripts(components, main_dir) + sync_builtin_lua_scripts(output_dir, manifest_path, manifest, copy_map, source_map) + return 0 + + +if __name__ == '__main__': + try: + sys.exit(main()) + except LuaScriptSyncError as exc: + print(f'sync_component_lua_scripts.py: error: {exc}', file=sys.stderr) + sys.exit(1) diff --git a/components/claw_capabilities/cap_boards/CMakeLists.txt b/components/claw_capabilities/cap_boards/CMakeLists.txt index 1372c55..61a7630 100644 --- a/components/claw_capabilities/cap_boards/CMakeLists.txt +++ b/components/claw_capabilities/cap_boards/CMakeLists.txt @@ -26,3 +26,4 @@ add_custom_command( add_custom_target(cap_boards_generate_skill DEPENDS "${CAP_BOARDS_OUTPUT_MD}") add_dependencies(${COMPONENT_LIB} cap_boards_generate_skill) set_property(GLOBAL APPEND PROPERTY BASIC_DEMO_SKILL_GENERATOR_TARGETS cap_boards_generate_skill) +set_property(GLOBAL APPEND PROPERTY BASIC_DEMO_RESOURCE_GENERATOR_TARGETS_skills cap_boards_generate_skill) diff --git a/application/basic_demo/fatfs_image/scripts/builtin/audio_demo.lua b/components/lua_modules/lua_module_audio/lua_scripts/basic_audio_record_play.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/audio_demo.lua rename to components/lua_modules/lua_module_audio/lua_scripts/basic_audio_record_play.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/button_demo.lua b/components/lua_modules/lua_module_button/lua_scripts/basic_button.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/button_demo.lua rename to components/lua_modules/lua_module_button/lua_scripts/basic_button.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/camera_capture_demo.lua b/components/lua_modules/lua_module_camera/lua_scripts/basic_camera_capture.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/camera_capture_demo.lua rename to components/lua_modules/lua_module_camera/lua_scripts/basic_camera_capture.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/display_demo.lua b/components/lua_modules/lua_module_display/lua_scripts/basic_display_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/display_demo.lua rename to components/lua_modules/lua_module_display/lua_scripts/basic_display_demo.lua diff --git a/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_llm_analyze.lua b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_llm_analyze.lua new file mode 100644 index 0000000..39667c2 --- /dev/null +++ b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_llm_analyze.lua @@ -0,0 +1,43 @@ +local event_publisher = require("event_publisher") + +local a = type(args) == "table" and args or {} + +local channel = a.channel +local chat_id = a.chat_id +local analyze_text = a.analyze_text or a.text or "Please analyze this text with llm: " + +local function require_string(name, value) + if type(value) ~= "string" or value == "" then + error("missing args." .. name) + end +end + +require_string("channel", channel) +require_string("chat_id", chat_id) + +print(string.format( + "[event_router_demo] publish trigger event_key=%s channel=%s chat_id=%s", + "lua_llm_analyze", + channel, + chat_id +)) + +event_publisher.publish({ + source_cap = "lua_script", + event_type = "trigger", + source_channel = channel, + target_channel = channel, + chat_id = chat_id, + content_type = "trigger", + message_id = "lua_llm_analyze", + correlation_id = "lua_llm_analyze", + text = analyze_text, + session_policy = "trigger", + payload = { + channel = channel, + chat_id = chat_id, + text = analyze_text, + }, +}) + +print("[event_router_demo] llm analyze trigger published") diff --git a/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_file.lua b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_file.lua new file mode 100644 index 0000000..c69f5ec --- /dev/null +++ b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_file.lua @@ -0,0 +1,72 @@ +local event_publisher = require("event_publisher") +local storage = require("storage") + +local a = type(args) == "table" and args or {} + +local channel = a.channel +local chat_id = a.chat_id +local file_caption = a.file_caption or a.caption or "lua trigger demo file" +local root_dir = storage.get_root_dir() +local demo_dir = storage.join_path(root_dir, "demo") +local demo_file_path = a.file_path or storage.join_path(demo_dir, "lua_event_router_demo.txt") + +local function require_string(name, value) + if type(value) ~= "string" or value == "" then + error("missing args." .. name) + end +end + +local function file_event_key_for_channel(name) + if name == "qq" then + return "lua_qq_send_file" + end + if name == "telegram" then + return "lua_tg_send_file" + end + if name == "feishu" then + return "lua_feishu_send_file" + end + error("file send only supports qq / telegram / feishu, got channel=" .. tostring(name)) +end + +require_string("channel", channel) +require_string("chat_id", chat_id) + +storage.mkdir(demo_dir) +storage.write_file(demo_file_path, table.concat({ + "lua event router demo", + "channel=" .. channel, + "chat_id=" .. chat_id, + "caption=" .. file_caption, +}, "\n") .. "\n") + +local event_key = file_event_key_for_channel(channel) + +print(string.format( + "[event_router_demo] publish trigger event_key=%s channel=%s chat_id=%s path=%s", + event_key, + channel, + chat_id, + demo_file_path +)) + +event_publisher.publish({ + source_cap = "lua_script", + event_type = "trigger", + source_channel = channel, + target_channel = channel, + chat_id = chat_id, + content_type = "trigger", + message_id = event_key, + correlation_id = event_key, + text = file_caption, + session_policy = "trigger", + payload = { + channel = channel, + chat_id = chat_id, + path = demo_file_path, + caption = file_caption, + }, +}) + +print("[event_router_demo] send file trigger published path=" .. demo_file_path) diff --git a/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_message.lua b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_message.lua new file mode 100644 index 0000000..ac710fc --- /dev/null +++ b/components/lua_modules/lua_module_event_publisher/lua_scripts/basic_router_send_message.lua @@ -0,0 +1,43 @@ +local event_publisher = require("event_publisher") + +local a = type(args) == "table" and args or {} + +local channel = a.channel +local chat_id = a.chat_id +local message_text = a.message_text or a.text or "hello from lua event_router" + +local function require_string(name, value) + if type(value) ~= "string" or value == "" then + error("missing args." .. name) + end +end + +require_string("channel", channel) +require_string("chat_id", chat_id) + +print(string.format( + "[event_router_demo] publish trigger event_key=%s channel=%s chat_id=%s", + "lua_im_send_message", + channel, + chat_id +)) + +event_publisher.publish({ + source_cap = "lua_script", + event_type = "trigger", + source_channel = channel, + target_channel = channel, + chat_id = chat_id, + content_type = "trigger", + message_id = "lua_im_send_message", + correlation_id = "lua_im_send_message", + text = message_text, + session_policy = "trigger", + payload = { + channel = channel, + chat_id = chat_id, + text = message_text, + }, +}) + +print("[event_router_demo] send message trigger published") diff --git a/application/basic_demo/fatfs_image/scripts/builtin/lcd_touch_demo.lua b/components/lua_modules/lua_module_lcd_touch/lua_scripts/basic_lcd_touch_demo.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/lcd_touch_demo.lua rename to components/lua_modules/lua_module_lcd_touch/lua_scripts/basic_lcd_touch_demo.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/led_strip_demo.lua b/components/lua_modules/lua_module_led_strip/lua_scripts/basic_led_strip.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/led_strip_demo.lua rename to components/lua_modules/lua_module_led_strip/lua_scripts/basic_led_strip.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/mcpwm_servo.lua b/components/lua_modules/lua_module_mcpwm/lua_scripts/basic_mcpwm_servo.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/mcpwm_servo.lua rename to components/lua_modules/lua_module_mcpwm/lua_scripts/basic_mcpwm_servo.lua diff --git a/application/basic_demo/fatfs_image/scripts/builtin/system_demo.lua b/components/lua_modules/lua_module_system/lua_scripts/basic_system_info.lua similarity index 100% rename from application/basic_demo/fatfs_image/scripts/builtin/system_demo.lua rename to components/lua_modules/lua_module_system/lua_scripts/basic_system_info.lua