diff --git a/application/basic_demo/CMakeLists.txt b/application/basic_demo/CMakeLists.txt index fab6c65..a115bfc 100644 --- a/application/basic_demo/CMakeLists.txt +++ b/application/basic_demo/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.16) -set(EXTRA_COMPONENT_DIRS "../../components") +set(EXTRA_COMPONENT_DIRS + "../../components" + "../../lua_modules") if(NOT DEFINED IDF_TARGET) set(IDF_TARGET "esp32s3" CACHE STRING "ESP-IDF target") endif() diff --git a/application/basic_demo/main/CMakeLists.txt b/application/basic_demo/main/CMakeLists.txt index d3b57f1..69c0a97 100644 --- a/application/basic_demo/main/CMakeLists.txt +++ b/application/basic_demo/main/CMakeLists.txt @@ -7,8 +7,6 @@ idf_component_register( "basic_demo_settings.c" "basic_demo_wifi.c" "config_http_server.c" - "lua_module/lua_module_gpio.c" - "lua_module/lua_module_led_strip.c" INCLUDE_DIRS "." REQUIRES @@ -39,6 +37,11 @@ idf_component_register( json nvs_flash wear_levelling + lua_module_delay + lua_module_event_publisher + lua_module_gpio + lua_module_led_strip + lua_module_storage EMBED_TXTFILES "web/index.html" "web/styles.css" diff --git a/application/basic_demo/main/basic_demo_lua_modules.c b/application/basic_demo/main/basic_demo_lua_modules.c index b2b16c3..b4498ab 100644 --- a/application/basic_demo/main/basic_demo_lua_modules.c +++ b/application/basic_demo/main/basic_demo_lua_modules.c @@ -5,18 +5,35 @@ */ #include "basic_demo_lua_modules.h" -#include "cap_lua.h" - -#include "lua_module/lua_module_gpio.h" -#include "lua_module/lua_module_led_strip.h" +#include "lua_module_delay.h" +#include "lua_module_event_publisher.h" +#include "lua_module_gpio.h" +#include "lua_module_led_strip.h" +#include "lua_module_storage.h" esp_err_t basic_demo_lua_modules_register(void) { - static const cap_lua_module_t s_modules[] = { - {.name = "gpio", .open_fn = luaopen_gpio}, - {.name = "led_strip", .open_fn = luaopen_led_strip}, - }; + esp_err_t err; - return cap_lua_register_modules(s_modules, - sizeof(s_modules) / sizeof(s_modules[0])); + err = lua_module_delay_register(); + if (err != ESP_OK) { + return err; + } + + err = lua_module_storage_register(); + if (err != ESP_OK) { + return err; + } + + err = lua_module_gpio_register(); + if (err != ESP_OK) { + return err; + } + + err = lua_module_led_strip_register(); + if (err != ESP_OK) { + return err; + } + + return lua_module_event_publisher_register(); } diff --git a/components/cap_lua/CMakeLists.txt b/components/cap_lua/CMakeLists.txt index 1cf53d6..8de499e 100644 --- a/components/cap_lua/CMakeLists.txt +++ b/components/cap_lua/CMakeLists.txt @@ -3,17 +3,15 @@ idf_component_register( "src/cap_lua.c" "src/cap_lua_runtime.c" "src/cap_lua_async.c" - "src/cap_lua_module_delay.c" - "src/cap_lua_storage.c" "src/cmd_cap_lua.c" INCLUDE_DIRS "include" "src" REQUIRES claw_cap + console esp_timer freertos georgik__lua json - console ) diff --git a/components/cap_lua/src/cap_lua.c b/components/cap_lua/src/cap_lua.c index 67208ea..33cb89d 100644 --- a/components/cap_lua/src/cap_lua.c +++ b/components/cap_lua/src/cap_lua.c @@ -879,20 +879,6 @@ esp_err_t cap_lua_register_modules(const cap_lua_module_t *modules, size_t count esp_err_t cap_lua_register_builtin_modules(void) { - static const cap_lua_module_t builtin_modules[] = { - {.name = "delay", .open_fn = luaopen_delay}, - {.name = "storage", .open_fn = luaopen_storage}, - }; - - if (s_builtin_modules_registered) { - return ESP_OK; - } - - ESP_RETURN_ON_ERROR(cap_lua_register_modules(builtin_modules, - sizeof(builtin_modules) / - sizeof(builtin_modules[0])), - TAG, - "Failed to register builtin Lua modules"); s_builtin_modules_registered = true; return ESP_OK; } diff --git a/components/cap_lua/src/cap_lua_internal.h b/components/cap_lua/src/cap_lua_internal.h index 108b98f..07c5681 100644 --- a/components/cap_lua/src/cap_lua_internal.h +++ b/components/cap_lua/src/cap_lua_internal.h @@ -64,6 +64,3 @@ esp_err_t cap_lua_async_list_jobs(const char *status_filter, esp_err_t cap_lua_async_get_job(const char *job_id, char *output, size_t output_size); - -int luaopen_delay(lua_State *L); -int luaopen_storage(lua_State *L); diff --git a/lua_modules/lua_module_delay/CMakeLists.txt b/lua_modules/lua_module_delay/CMakeLists.txt new file mode 100644 index 0000000..66e7be3 --- /dev/null +++ b/lua_modules/lua_module_delay/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "src/lua_module_delay.c" + INCLUDE_DIRS + "include" + REQUIRES + cap_lua + freertos +) diff --git a/lua_modules/lua_module_delay/include/lua_module_delay.h b/lua_modules/lua_module_delay/include/lua_module_delay.h new file mode 100644 index 0000000..87d6914 --- /dev/null +++ b/lua_modules/lua_module_delay/include/lua_module_delay.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "lua.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int luaopen_delay(lua_State *L); +esp_err_t lua_module_delay_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/cap_lua/src/cap_lua_module_delay.c b/lua_modules/lua_module_delay/src/lua_module_delay.c similarity index 64% rename from components/cap_lua/src/cap_lua_module_delay.c rename to lua_modules/lua_module_delay/src/lua_module_delay.c index 3e057a5..0a26ed9 100644 --- a/components/cap_lua/src/cap_lua_module_delay.c +++ b/lua_modules/lua_module_delay/src/lua_module_delay.c @@ -3,15 +3,16 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "cap_lua_internal.h" +#include "lua_module_delay.h" #include +#include "cap_lua.h" #include "lauxlib.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -static int cap_lua_delay_ms(lua_State *L) +static int lua_module_delay_sleep_ms(lua_State *L) { lua_Integer ms = luaL_checkinteger(L, 1); @@ -26,7 +27,12 @@ static int cap_lua_delay_ms(lua_State *L) int luaopen_delay(lua_State *L) { lua_newtable(L); - lua_pushcfunction(L, cap_lua_delay_ms); + lua_pushcfunction(L, lua_module_delay_sleep_ms); lua_setfield(L, -2, "delay_ms"); return 1; } + +esp_err_t lua_module_delay_register(void) +{ + return cap_lua_register_module("delay", luaopen_delay); +} diff --git a/lua_modules/lua_module_event_publisher/CMakeLists.txt b/lua_modules/lua_module_event_publisher/CMakeLists.txt new file mode 100644 index 0000000..52fa230 --- /dev/null +++ b/lua_modules/lua_module_event_publisher/CMakeLists.txt @@ -0,0 +1,11 @@ +idf_component_register( + SRCS + "src/lua_module_event_publisher.c" + INCLUDE_DIRS + "include" + REQUIRES + cap_lua + claw_event_router + esp_timer + json +) diff --git a/lua_modules/lua_module_event_publisher/include/lua_module_event_publisher.h b/lua_modules/lua_module_event_publisher/include/lua_module_event_publisher.h new file mode 100644 index 0000000..4112b33 --- /dev/null +++ b/lua_modules/lua_module_event_publisher/include/lua_module_event_publisher.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "lua.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int luaopen_event_publisher(lua_State *L); +esp_err_t lua_module_event_publisher_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/lua_modules/lua_module_event_publisher/src/lua_module_event_publisher.c b/lua_modules/lua_module_event_publisher/src/lua_module_event_publisher.c new file mode 100644 index 0000000..d5741bd --- /dev/null +++ b/lua_modules/lua_module_event_publisher/src/lua_module_event_publisher.c @@ -0,0 +1,425 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "lua_module_event_publisher.h" + +#include +#include +#include +#include +#include + +#include "cJSON.h" +#include "claw_event_router.h" +#include "esp_err.h" +#include "esp_timer.h" +#include "lauxlib.h" + +#include "cap_lua.h" + +static const char *LUA_MODULE_EVENT_PUBLISHER_NAME = "event_publisher"; + +static const char *lua_table_get_string_field(lua_State *L, + int index, + const char *field_name, + bool required) +{ + const char *value = NULL; + int type; + + lua_getfield(L, index, field_name); + type = lua_type(L, -1); + if (type == LUA_TNIL) { + lua_pop(L, 1); + if (required) { + luaL_error(L, "missing required field '%s'", field_name); + } + return NULL; + } + + if (type != LUA_TSTRING) { + lua_pop(L, 1); + luaL_error(L, "field '%s' must be a string", field_name); + } + + value = lua_tostring(L, -1); + lua_pop(L, 1); + return value; +} + +static bool lua_table_get_integer_field(lua_State *L, + int index, + const char *field_name, + int64_t *out_value) +{ + int is_num = 0; + + lua_getfield(L, index, field_name); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return false; + } + + *out_value = (int64_t)lua_tointegerx(L, -1, &is_num); + lua_pop(L, 1); + if (!is_num) { + luaL_error(L, "field '%s' must be an integer", field_name); + } + return true; +} + +static cJSON *lua_module_event_publisher_json_from_value(lua_State *L, int index); + +static bool lua_module_event_publisher_table_is_array(lua_State *L, int index) +{ + lua_Integer expected = 1; + bool has_entries = false; + + index = lua_absindex(L, index); + lua_pushnil(L); + while (lua_next(L, index) != 0) { + if (!lua_isinteger(L, -2) || lua_tointeger(L, -2) != expected) { + lua_pop(L, 2); + return false; + } + has_entries = true; + expected++; + lua_pop(L, 1); + } + + return has_entries; +} + +static cJSON *lua_module_event_publisher_json_from_table(lua_State *L, int index) +{ + cJSON *json = NULL; + + index = lua_absindex(L, index); + if (lua_module_event_publisher_table_is_array(L, index)) { + json = cJSON_CreateArray(); + lua_pushnil(L); + while (lua_next(L, index) != 0) { + cJSON *child = lua_module_event_publisher_json_from_value(L, -1); + + lua_pop(L, 1); + if (!child || !cJSON_AddItemToArray(json, child)) { + cJSON_Delete(child); + cJSON_Delete(json); + return NULL; + } + } + return json; + } + + json = cJSON_CreateObject(); + lua_pushnil(L); + while (lua_next(L, index) != 0) { + const char *key = NULL; + char key_buf[32]; + cJSON *child = NULL; + + if (lua_type(L, -2) == LUA_TSTRING) { + key = lua_tostring(L, -2); + } else if (lua_isinteger(L, -2)) { + snprintf(key_buf, sizeof(key_buf), "%" PRId64, (int64_t)lua_tointeger(L, -2)); + key = key_buf; + } else { + lua_pop(L, 2); + cJSON_Delete(json); + return NULL; + } + + child = lua_module_event_publisher_json_from_value(L, -1); + lua_pop(L, 1); + if (!child || !cJSON_AddItemToObject(json, key, child)) { + cJSON_Delete(child); + cJSON_Delete(json); + return NULL; + } + } + + return json; +} + +static cJSON *lua_module_event_publisher_json_from_value(lua_State *L, int index) +{ + index = lua_absindex(L, index); + + switch (lua_type(L, index)) { + case LUA_TNIL: + return cJSON_CreateNull(); + case LUA_TBOOLEAN: + return cJSON_CreateBool(lua_toboolean(L, index)); + case LUA_TNUMBER: + return cJSON_CreateNumber(lua_tonumber(L, index)); + case LUA_TSTRING: + return cJSON_CreateString(lua_tostring(L, index)); + case LUA_TTABLE: + return lua_module_event_publisher_json_from_table(L, index); + default: + return NULL; + } +} + +static char *lua_table_get_payload_json_field(lua_State *L, int index, bool default_empty_object) +{ + char *payload_json = NULL; + cJSON *json = NULL; + + lua_getfield(L, index, "payload_json"); + if (!lua_isnil(L, -1)) { + if (!lua_isstring(L, -1)) { + lua_pop(L, 1); + luaL_error(L, "field 'payload_json' must be a string"); + } + payload_json = strdup(lua_tostring(L, -1)); + lua_pop(L, 1); + if (!payload_json) { + luaL_error(L, "out of memory"); + } + return payload_json; + } + lua_pop(L, 1); + + lua_getfield(L, index, "payload"); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + if (!default_empty_object) { + return NULL; + } + payload_json = strdup("{}"); + if (!payload_json) { + luaL_error(L, "out of memory"); + } + return payload_json; + } + + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + luaL_error(L, "field 'payload' must be a table"); + } + + json = lua_module_event_publisher_json_from_value(L, -1); + lua_pop(L, 1); + if (!json) { + luaL_error(L, "failed to convert 'payload' to JSON"); + } + + payload_json = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + if (!payload_json) { + luaL_error(L, "failed to serialize 'payload' as JSON"); + } + + return payload_json; +} + +static claw_event_session_policy_t lua_module_event_publisher_parse_session_policy(lua_State *L, + int index, + bool *has_value) +{ + const char *policy = NULL; + + *has_value = false; + lua_getfield(L, index, "session_policy"); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return CLAW_EVENT_SESSION_POLICY_CHAT; + } + if (!lua_isstring(L, -1)) { + lua_pop(L, 1); + luaL_error(L, "field 'session_policy' must be a string"); + } + + *has_value = true; + policy = lua_tostring(L, -1); + lua_pop(L, 1); + if (strcmp(policy, "chat") == 0) { + return CLAW_EVENT_SESSION_POLICY_CHAT; + } + if (strcmp(policy, "trigger") == 0) { + return CLAW_EVENT_SESSION_POLICY_TRIGGER; + } + if (strcmp(policy, "global") == 0) { + return CLAW_EVENT_SESSION_POLICY_GLOBAL; + } + if (strcmp(policy, "ephemeral") == 0) { + return CLAW_EVENT_SESSION_POLICY_EPHEMERAL; + } + if (strcmp(policy, "nosave") == 0) { + return CLAW_EVENT_SESSION_POLICY_NOSAVE; + } + + luaL_error(L, "invalid session_policy '%s'", policy); + return CLAW_EVENT_SESSION_POLICY_CHAT; +} + +static void lua_module_event_publisher_copy_field(char *dst, + size_t dst_size, + const char *value) +{ + if (value) { + strlcpy(dst, value, dst_size); + } +} + +static void lua_module_event_publisher_raise_publish_error(lua_State *L, esp_err_t err) +{ + luaL_error(L, "event publish failed: %s", esp_err_to_name(err)); +} + +static int lua_event_publisher_publish_message(lua_State *L) +{ + const char *source_cap = NULL; + const char *channel = NULL; + const char *chat_id = NULL; + const char *text = NULL; + const char *sender_id = NULL; + const char *message_id = NULL; + esp_err_t err; + + luaL_checktype(L, 1, LUA_TTABLE); + source_cap = lua_table_get_string_field(L, 1, "source_cap", true); + channel = lua_table_get_string_field(L, 1, "channel", true); + chat_id = lua_table_get_string_field(L, 1, "chat_id", true); + text = lua_table_get_string_field(L, 1, "text", true); + sender_id = lua_table_get_string_field(L, 1, "sender_id", false); + message_id = lua_table_get_string_field(L, 1, "message_id", false); + + err = claw_event_router_publish_message(source_cap, + channel, + chat_id, + text, + sender_id, + message_id); + if (err != ESP_OK) { + lua_module_event_publisher_raise_publish_error(L, err); + } + + lua_pushboolean(L, 1); + return 1; +} + +static int lua_event_publisher_publish_trigger(lua_State *L) +{ + const char *source_cap = NULL; + const char *event_type = NULL; + const char *event_key = NULL; + char *payload_json = NULL; + esp_err_t err; + + luaL_checktype(L, 1, LUA_TTABLE); + source_cap = lua_table_get_string_field(L, 1, "source_cap", true); + event_type = lua_table_get_string_field(L, 1, "event_type", true); + event_key = lua_table_get_string_field(L, 1, "event_key", true); + payload_json = lua_table_get_payload_json_field(L, 1, true); + + err = claw_event_router_publish_trigger(source_cap, + event_type, + event_key, + payload_json); + free(payload_json); + if (err != ESP_OK) { + lua_module_event_publisher_raise_publish_error(L, err); + } + + lua_pushboolean(L, 1); + return 1; +} + +static int lua_event_publisher_publish(lua_State *L) +{ + claw_event_t event = {0}; + const char *event_id = NULL; + const char *source_cap = NULL; + const char *event_type = NULL; + const char *source_channel = NULL; + const char *target_channel = NULL; + const char *source_endpoint = NULL; + const char *target_endpoint = NULL; + const char *chat_id = NULL; + const char *sender_id = NULL; + const char *message_id = NULL; + const char *correlation_id = NULL; + const char *content_type = NULL; + const char *text = NULL; + char *payload_json = NULL; + int64_t timestamp_ms = 0; + bool has_timestamp = false; + bool has_policy = false; + esp_err_t err; + + luaL_checktype(L, 1, LUA_TTABLE); + + source_cap = lua_table_get_string_field(L, 1, "source_cap", true); + event_type = lua_table_get_string_field(L, 1, "event_type", true); + event_id = lua_table_get_string_field(L, 1, "event_id", false); + source_channel = lua_table_get_string_field(L, 1, "source_channel", false); + target_channel = lua_table_get_string_field(L, 1, "target_channel", false); + source_endpoint = lua_table_get_string_field(L, 1, "source_endpoint", false); + target_endpoint = lua_table_get_string_field(L, 1, "target_endpoint", false); + chat_id = lua_table_get_string_field(L, 1, "chat_id", false); + sender_id = lua_table_get_string_field(L, 1, "sender_id", false); + message_id = lua_table_get_string_field(L, 1, "message_id", false); + correlation_id = lua_table_get_string_field(L, 1, "correlation_id", false); + content_type = lua_table_get_string_field(L, 1, "content_type", false); + text = lua_table_get_string_field(L, 1, "text", false); + payload_json = lua_table_get_payload_json_field(L, 1, false); + has_timestamp = lua_table_get_integer_field(L, 1, "timestamp_ms", ×tamp_ms); + + lua_module_event_publisher_copy_field(event.source_cap, sizeof(event.source_cap), source_cap); + lua_module_event_publisher_copy_field(event.event_type, sizeof(event.event_type), event_type); + lua_module_event_publisher_copy_field(event.source_channel, sizeof(event.source_channel), source_channel); + lua_module_event_publisher_copy_field(event.target_channel, sizeof(event.target_channel), target_channel); + lua_module_event_publisher_copy_field(event.source_endpoint, sizeof(event.source_endpoint), source_endpoint); + lua_module_event_publisher_copy_field(event.target_endpoint, sizeof(event.target_endpoint), target_endpoint); + lua_module_event_publisher_copy_field(event.chat_id, sizeof(event.chat_id), chat_id); + lua_module_event_publisher_copy_field(event.sender_id, sizeof(event.sender_id), sender_id); + lua_module_event_publisher_copy_field(event.message_id, sizeof(event.message_id), message_id); + lua_module_event_publisher_copy_field(event.correlation_id, sizeof(event.correlation_id), correlation_id); + lua_module_event_publisher_copy_field(event.content_type, sizeof(event.content_type), content_type); + + if (event_id) { + lua_module_event_publisher_copy_field(event.event_id, sizeof(event.event_id), event_id); + } else { + snprintf(event.event_id, sizeof(event.event_id), "lua-%" PRId64, esp_timer_get_time() / 1000); + } + + event.timestamp_ms = has_timestamp ? timestamp_ms : (esp_timer_get_time() / 1000); + event.session_policy = lua_module_event_publisher_parse_session_policy(L, 1, &has_policy); + if (!has_policy && strcmp(event_type, "trigger") == 0) { + event.session_policy = CLAW_EVENT_SESSION_POLICY_TRIGGER; + } + + event.text = (char *)text; + event.payload_json = payload_json; + + err = claw_event_router_publish(&event); + free(payload_json); + if (err != ESP_OK) { + lua_module_event_publisher_raise_publish_error(L, err); + } + + lua_pushboolean(L, 1); + return 1; +} + +int luaopen_event_publisher(lua_State *L) +{ + lua_newtable(L); + lua_pushcfunction(L, lua_event_publisher_publish); + lua_setfield(L, -2, "publish"); + lua_pushcfunction(L, lua_event_publisher_publish_message); + lua_setfield(L, -2, "publish_message"); + lua_pushcfunction(L, lua_event_publisher_publish_trigger); + lua_setfield(L, -2, "publish_trigger"); + return 1; +} + +esp_err_t lua_module_event_publisher_register(void) +{ + return cap_lua_register_module(LUA_MODULE_EVENT_PUBLISHER_NAME, + luaopen_event_publisher); +} diff --git a/lua_modules/lua_module_gpio/CMakeLists.txt b/lua_modules/lua_module_gpio/CMakeLists.txt new file mode 100644 index 0000000..cb45135 --- /dev/null +++ b/lua_modules/lua_module_gpio/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "src/lua_module_gpio.c" + INCLUDE_DIRS + "include" + REQUIRES + cap_lua + driver +) diff --git a/application/basic_demo/main/lua_module/lua_module_gpio.h b/lua_modules/lua_module_gpio/include/lua_module_gpio.h similarity index 79% rename from application/basic_demo/main/lua_module/lua_module_gpio.h rename to lua_modules/lua_module_gpio/include/lua_module_gpio.h index 0a9cf5c..f673cad 100644 --- a/application/basic_demo/main/lua_module/lua_module_gpio.h +++ b/lua_modules/lua_module_gpio/include/lua_module_gpio.h @@ -5,6 +5,7 @@ */ #pragma once +#include "esp_err.h" #include "lua.h" #ifdef __cplusplus @@ -12,6 +13,7 @@ extern "C" { #endif int luaopen_gpio(lua_State *L); +esp_err_t lua_module_gpio_register(void); #ifdef __cplusplus } diff --git a/application/basic_demo/main/lua_module/lua_module_gpio.c b/lua_modules/lua_module_gpio/src/lua_module_gpio.c similarity index 72% rename from application/basic_demo/main/lua_module/lua_module_gpio.c rename to lua_modules/lua_module_gpio/src/lua_module_gpio.c index abb3cf1..50c49a7 100644 --- a/application/basic_demo/main/lua_module/lua_module_gpio.c +++ b/lua_modules/lua_module_gpio/src/lua_module_gpio.c @@ -3,15 +3,16 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "lua_module/lua_module_gpio.h" +#include "lua_module_gpio.h" #include +#include "cap_lua.h" #include "driver/gpio.h" #include "esp_err.h" #include "lauxlib.h" -static gpio_mode_t gpio_mode_from_string(const char *mode) +static gpio_mode_t lua_module_gpio_mode_from_string(const char *mode) { if (!mode || strcmp(mode, "input") == 0) { return GPIO_MODE_INPUT; @@ -31,11 +32,11 @@ static gpio_mode_t gpio_mode_from_string(const char *mode) return GPIO_MODE_DISABLE; } -static int lua_gpio_set_direction(lua_State *L) +static int lua_module_gpio_set_direction(lua_State *L) { gpio_num_t pin = (gpio_num_t)luaL_checkinteger(L, 1); const char *mode_str = luaL_checkstring(L, 2); - gpio_mode_t mode = gpio_mode_from_string(mode_str); + gpio_mode_t mode = lua_module_gpio_mode_from_string(mode_str); if (mode == GPIO_MODE_DISABLE && strcmp(mode_str, "disable") != 0) { return luaL_error(L, "invalid gpio mode: %s", mode_str); @@ -48,7 +49,7 @@ static int lua_gpio_set_direction(lua_State *L) return 0; } -static int lua_gpio_set_level(lua_State *L) +static int lua_module_gpio_set_level(lua_State *L) { gpio_num_t pin = (gpio_num_t)luaL_checkinteger(L, 1); uint32_t level = (uint32_t)luaL_checkinteger(L, 2); @@ -60,7 +61,7 @@ static int lua_gpio_set_level(lua_State *L) return 0; } -static int lua_gpio_get_level(lua_State *L) +static int lua_module_gpio_get_level(lua_State *L) { gpio_num_t pin = (gpio_num_t)luaL_checkinteger(L, 1); @@ -71,11 +72,16 @@ static int lua_gpio_get_level(lua_State *L) int luaopen_gpio(lua_State *L) { lua_newtable(L); - lua_pushcfunction(L, lua_gpio_set_direction); + lua_pushcfunction(L, lua_module_gpio_set_direction); lua_setfield(L, -2, "set_direction"); - lua_pushcfunction(L, lua_gpio_set_level); + lua_pushcfunction(L, lua_module_gpio_set_level); lua_setfield(L, -2, "set_level"); - lua_pushcfunction(L, lua_gpio_get_level); + lua_pushcfunction(L, lua_module_gpio_get_level); lua_setfield(L, -2, "get_level"); return 1; } + +esp_err_t lua_module_gpio_register(void) +{ + return cap_lua_register_module("gpio", luaopen_gpio); +} diff --git a/lua_modules/lua_module_led_strip/CMakeLists.txt b/lua_modules/lua_module_led_strip/CMakeLists.txt new file mode 100644 index 0000000..ae9c628 --- /dev/null +++ b/lua_modules/lua_module_led_strip/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "src/lua_module_led_strip.c" + INCLUDE_DIRS + "include" + REQUIRES + cap_lua + espressif__led_strip +) diff --git a/application/basic_demo/main/lua_module/lua_module_led_strip.h b/lua_modules/lua_module_led_strip/include/lua_module_led_strip.h similarity index 57% rename from application/basic_demo/main/lua_module/lua_module_led_strip.h rename to lua_modules/lua_module_led_strip/include/lua_module_led_strip.h index 00372d5..7c94471 100644 --- a/application/basic_demo/main/lua_module/lua_module_led_strip.h +++ b/lua_modules/lua_module_led_strip/include/lua_module_led_strip.h @@ -5,6 +5,16 @@ */ #pragma once +#include "esp_err.h" #include "lua.h" +#ifdef __cplusplus +extern "C" { +#endif + int luaopen_led_strip(lua_State *L); +esp_err_t lua_module_led_strip_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/application/basic_demo/main/lua_module/lua_module_led_strip.c b/lua_modules/lua_module_led_strip/src/lua_module_led_strip.c similarity index 57% rename from application/basic_demo/main/lua_module/lua_module_led_strip.c rename to lua_modules/lua_module_led_strip/src/lua_module_led_strip.c index 0c06c48..9d30300 100644 --- a/application/basic_demo/main/lua_module/lua_module_led_strip.c +++ b/lua_modules/lua_module_led_strip/src/lua_module_led_strip.c @@ -5,32 +5,34 @@ */ #include "lua_module_led_strip.h" -#include +#include "cap_lua.h" #include "esp_err.h" #include "lauxlib.h" #include "led_strip.h" #include "led_strip_rmt.h" #include "led_strip_types.h" -#define LUA_LED_STRIP_METATABLE "led_strip" -#define LUA_LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000) +#define LUA_MODULE_LED_STRIP_METATABLE "led_strip" +#define LUA_MODULE_LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000) typedef struct { led_strip_handle_t strip; -} lua_led_strip_ud_t; +} lua_module_led_strip_ud_t; -static lua_led_strip_ud_t *lua_led_strip_get_ud(lua_State *L, int idx) +static lua_module_led_strip_ud_t *lua_module_led_strip_get_ud(lua_State *L, int idx) { - lua_led_strip_ud_t *ud = (lua_led_strip_ud_t *)luaL_checkudata(L, idx, LUA_LED_STRIP_METATABLE); + lua_module_led_strip_ud_t *ud = + (lua_module_led_strip_ud_t *)luaL_checkudata(L, idx, LUA_MODULE_LED_STRIP_METATABLE); if (!ud || !ud->strip) { luaL_error(L, "led_strip: invalid or closed handle"); } return ud; } -static int lua_led_strip_gc(lua_State *L) +static int lua_module_led_strip_gc(lua_State *L) { - lua_led_strip_ud_t *ud = (lua_led_strip_ud_t *)luaL_testudata(L, 1, LUA_LED_STRIP_METATABLE); + lua_module_led_strip_ud_t *ud = + (lua_module_led_strip_ud_t *)luaL_testudata(L, 1, LUA_MODULE_LED_STRIP_METATABLE); if (ud && ud->strip) { led_strip_del(ud->strip); ud->strip = NULL; @@ -38,9 +40,9 @@ static int lua_led_strip_gc(lua_State *L) return 0; } -static int lua_led_strip_set_pixel(lua_State *L) +static int lua_module_led_strip_set_pixel(lua_State *L) { - lua_led_strip_ud_t *ud = lua_led_strip_get_ud(L, 1); + lua_module_led_strip_ud_t *ud = lua_module_led_strip_get_ud(L, 1); uint32_t index = (uint32_t)luaL_checkinteger(L, 2); uint32_t r = (uint32_t)luaL_checkinteger(L, 3); uint32_t g = (uint32_t)luaL_checkinteger(L, 4); @@ -52,9 +54,9 @@ static int lua_led_strip_set_pixel(lua_State *L) return 0; } -static int lua_led_strip_refresh(lua_State *L) +static int lua_module_led_strip_refresh(lua_State *L) { - lua_led_strip_ud_t *ud = lua_led_strip_get_ud(L, 1); + lua_module_led_strip_ud_t *ud = lua_module_led_strip_get_ud(L, 1); esp_err_t err = led_strip_refresh(ud->strip); if (err != ESP_OK) { return luaL_error(L, "led_strip refresh failed: %s", esp_err_to_name(err)); @@ -62,9 +64,9 @@ static int lua_led_strip_refresh(lua_State *L) return 0; } -static int lua_led_strip_clear(lua_State *L) +static int lua_module_led_strip_clear(lua_State *L) { - lua_led_strip_ud_t *ud = lua_led_strip_get_ud(L, 1); + lua_module_led_strip_ud_t *ud = lua_module_led_strip_get_ud(L, 1); esp_err_t err = led_strip_clear(ud->strip); if (err != ESP_OK) { return luaL_error(L, "led_strip clear failed: %s", esp_err_to_name(err)); @@ -72,9 +74,10 @@ static int lua_led_strip_clear(lua_State *L) return 0; } -static int lua_led_strip_close(lua_State *L) +static int lua_module_led_strip_close(lua_State *L) { - lua_led_strip_ud_t *ud = (lua_led_strip_ud_t *)luaL_checkudata(L, 1, LUA_LED_STRIP_METATABLE); + lua_module_led_strip_ud_t *ud = + (lua_module_led_strip_ud_t *)luaL_checkudata(L, 1, LUA_MODULE_LED_STRIP_METATABLE); if (ud->strip) { led_strip_del(ud->strip); ud->strip = NULL; @@ -82,10 +85,13 @@ static int lua_led_strip_close(lua_State *L) return 0; } -static int lua_led_strip_new(lua_State *L) +static int lua_module_led_strip_new(lua_State *L) { int gpio = (int)luaL_checkinteger(L, 1); int max_leds = (int)luaL_checkinteger(L, 2); + led_strip_handle_t strip = NULL; + esp_err_t err; + if (max_leds <= 0) { return luaL_error(L, "max_leds must be positive"); } @@ -101,46 +107,51 @@ static int lua_led_strip_new(lua_State *L) }; led_strip_rmt_config_t rmt_config = { .clk_src = RMT_CLK_SRC_DEFAULT, - .resolution_hz = LUA_LED_STRIP_RMT_RES_HZ, + .resolution_hz = LUA_MODULE_LED_STRIP_RMT_RES_HZ, .mem_block_symbols = 0, .flags = { .with_dma = 0, } }; - led_strip_handle_t strip = NULL; - esp_err_t err = led_strip_new_rmt_device(&strip_config, &rmt_config, &strip); + err = led_strip_new_rmt_device(&strip_config, &rmt_config, &strip); if (err != ESP_OK) { return luaL_error(L, "led_strip new failed: %s", esp_err_to_name(err)); } - lua_led_strip_ud_t *ud = (lua_led_strip_ud_t *)lua_newuserdata(L, sizeof(*ud)); + lua_module_led_strip_ud_t *ud = + (lua_module_led_strip_ud_t *)lua_newuserdata(L, sizeof(*ud)); ud->strip = strip; - luaL_getmetatable(L, LUA_LED_STRIP_METATABLE); + luaL_getmetatable(L, LUA_MODULE_LED_STRIP_METATABLE); lua_setmetatable(L, -2); return 1; } int luaopen_led_strip(lua_State *L) { - if (luaL_newmetatable(L, LUA_LED_STRIP_METATABLE)) { - lua_pushcfunction(L, lua_led_strip_gc); + if (luaL_newmetatable(L, LUA_MODULE_LED_STRIP_METATABLE)) { + lua_pushcfunction(L, lua_module_led_strip_gc); lua_setfield(L, -2, "__gc"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, lua_led_strip_set_pixel); + lua_pushcfunction(L, lua_module_led_strip_set_pixel); lua_setfield(L, -2, "set_pixel"); - lua_pushcfunction(L, lua_led_strip_refresh); + lua_pushcfunction(L, lua_module_led_strip_refresh); lua_setfield(L, -2, "refresh"); - lua_pushcfunction(L, lua_led_strip_clear); + lua_pushcfunction(L, lua_module_led_strip_clear); lua_setfield(L, -2, "clear"); - lua_pushcfunction(L, lua_led_strip_close); + lua_pushcfunction(L, lua_module_led_strip_close); lua_setfield(L, -2, "close"); } lua_pop(L, 1); lua_newtable(L); - lua_pushcfunction(L, lua_led_strip_new); + lua_pushcfunction(L, lua_module_led_strip_new); lua_setfield(L, -2, "new"); return 1; } + +esp_err_t lua_module_led_strip_register(void) +{ + return cap_lua_register_module("led_strip", luaopen_led_strip); +} diff --git a/lua_modules/lua_module_storage/CMakeLists.txt b/lua_modules/lua_module_storage/CMakeLists.txt new file mode 100644 index 0000000..3a9bed6 --- /dev/null +++ b/lua_modules/lua_module_storage/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + "src/lua_module_storage.c" + INCLUDE_DIRS + "include" + REQUIRES + cap_lua +) diff --git a/lua_modules/lua_module_storage/include/lua_module_storage.h b/lua_modules/lua_module_storage/include/lua_module_storage.h new file mode 100644 index 0000000..1386362 --- /dev/null +++ b/lua_modules/lua_module_storage/include/lua_module_storage.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "lua.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int luaopen_storage(lua_State *L); +esp_err_t lua_module_storage_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/cap_lua/src/cap_lua_storage.c b/lua_modules/lua_module_storage/src/lua_module_storage.c similarity index 81% rename from components/cap_lua/src/cap_lua_storage.c rename to lua_modules/lua_module_storage/src/lua_module_storage.c index ccb4e11..3ae4c86 100644 --- a/components/cap_lua/src/cap_lua_storage.c +++ b/lua_modules/lua_module_storage/src/lua_module_storage.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "cap_lua_internal.h" +#include "lua_module_storage.h" #include #include @@ -11,9 +11,10 @@ #include #include +#include "cap_lua.h" #include "lauxlib.h" -static int cap_lua_storage_mkdir(lua_State *L) +static int lua_module_storage_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -25,7 +26,7 @@ static int cap_lua_storage_mkdir(lua_State *L) return 1; } -static int cap_lua_storage_write_file(lua_State *L) +static int lua_module_storage_write_file(lua_State *L) { const char *path = luaL_checkstring(L, 1); size_t content_len = 0; @@ -47,7 +48,7 @@ static int cap_lua_storage_write_file(lua_State *L) return 1; } -static int cap_lua_storage_read_file(lua_State *L) +static int lua_module_storage_read_file(lua_State *L) { const char *path = luaL_checkstring(L, 1); FILE *file = NULL; @@ -96,11 +97,16 @@ static int cap_lua_storage_read_file(lua_State *L) int luaopen_storage(lua_State *L) { lua_newtable(L); - lua_pushcfunction(L, cap_lua_storage_mkdir); + lua_pushcfunction(L, lua_module_storage_mkdir); lua_setfield(L, -2, "mkdir"); - lua_pushcfunction(L, cap_lua_storage_write_file); + lua_pushcfunction(L, lua_module_storage_write_file); lua_setfield(L, -2, "write_file"); - lua_pushcfunction(L, cap_lua_storage_read_file); + lua_pushcfunction(L, lua_module_storage_read_file); lua_setfield(L, -2, "read_file"); return 1; } + +esp_err_t lua_module_storage_register(void) +{ + return cap_lua_register_module("storage", luaopen_storage); +}