Skip to content

Commit 57e536e

Browse files
authored
Merge pull request #116 from zevorn/feat/issue-115-script-runtimes
platform: add Python script runtimes
2 parents 5811922 + e04f412 commit 57e536e

24 files changed

Lines changed: 1435 additions & 59 deletions

.gitmodules

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@
99
[submodule "vendor/lib/freertos-plus-tcp"]
1010
path = vendor/lib/freertos-plus-tcp
1111
url = https://github.com/FreeRTOS/FreeRTOS-Plus-TCP.git
12+
[submodule "third_party/rtthread-micropython"]
13+
path = third_party/rtthread-micropython
14+
url = https://github.com/RT-Thread-packages/micropython.git
15+
branch = master
16+
ignore = untracked

Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ A9_PLATFORM := platform/vexpress-a9
9090
.PHONY: vexpress-a9-qemu
9191
vexpress-a9-qemu:
9292
@if [ ! -f $(MESON_BUILDDIR_A9)/build.ninja ]; then \
93-
meson setup $(MESON_BUILDDIR_A9) --cross-file $(CROSS_FILE_A9); \
93+
meson setup $(MESON_BUILDDIR_A9) --cross-file $(CROSS_FILE_A9) \
94+
-Dtool_script=true; \
95+
else \
96+
meson configure $(MESON_BUILDDIR_A9) -Dtool_script=true; \
9497
fi
9598
meson compile -C $(MESON_BUILDDIR_A9)
9699
cd $(A9_PLATFORM) && scons -j$$(nproc)
@@ -374,7 +377,10 @@ MESON_BUILDDIR_LINUX := $(BUILD_DIR)/linux
374377
.PHONY: build-linux
375378
build-linux:
376379
@if [ ! -f $(MESON_BUILDDIR_LINUX)/build.ninja ]; then \
377-
meson setup $(MESON_BUILDDIR_LINUX) -Dosal=linux; \
380+
meson setup $(MESON_BUILDDIR_LINUX) -Dosal=linux \
381+
-Dtool_script=true; \
382+
else \
383+
meson configure $(MESON_BUILDDIR_LINUX) -Dtool_script=true; \
378384
fi
379385
meson compile -C $(MESON_BUILDDIR_LINUX)
380386
@echo "Output: $(MESON_BUILDDIR_LINUX)/platform/linux/rtclaw"
@@ -526,6 +532,7 @@ test-smoke-esp32s3:
526532
.PHONY: test-smoke-vexpress
527533
test-smoke-vexpress: vexpress-a9-qemu
528534
$(call _functest,vexpress-a9-qemu,test_boot.py)
535+
$(call _functest,vexpress-a9-qemu,test_vexpress_micropython.py)
529536

530537
.PHONY: test-online-esp32c3
531538
test-online-esp32c3: run-esp32c3-qemu-flash

claw/meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ if get_option('tool_net')
6262
feature_defs += '-DCONFIG_RTCLAW_TOOL_NET'
6363
src_files += files('services/tools/net.c')
6464
endif
65+
if get_option('tool_script')
66+
feature_defs += '-DCONFIG_RTCLAW_TOOL_SCRIPT'
67+
src_files += files('services/tools/script.c')
68+
endif
6569
if get_option('tool_mouse')
6670
feature_defs += '-DCONFIG_RTCLAW_TOOL_MOUSE'
6771
src_files += files('services/tools/mouse.c')

claw/services/tools/script.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2026, Chao Liu <chao.liu.zevorn@gmail.com>
3+
* SPDX-License-Identifier: MIT
4+
*
5+
* Script execution tool.
6+
*/
7+
8+
#include "claw/services/tools/tools.h"
9+
#include "platform/scripting.h"
10+
11+
#include <stdio.h>
12+
#include <string.h>
13+
14+
#define SCRIPT_OUTPUT_MAX 1024
15+
#define SCRIPT_LANGUAGE_MICROPYTHON "micropython"
16+
17+
__attribute__((weak))
18+
int claw_platform_script_supported(const char *language)
19+
{
20+
(void)language;
21+
return 0;
22+
}
23+
24+
__attribute__((weak))
25+
int claw_platform_run_script(const char *language, const char *code,
26+
char *output, size_t output_size)
27+
{
28+
(void)language;
29+
(void)code;
30+
if (output && output_size > 0) {
31+
output[0] = '\0';
32+
}
33+
return -1;
34+
}
35+
36+
static const char *script_language_from_params(const cJSON *params)
37+
{
38+
const cJSON *language = cJSON_GetObjectItem(params, "language");
39+
40+
if (language && cJSON_IsString(language)) {
41+
return language->valuestring;
42+
}
43+
44+
return SCRIPT_LANGUAGE_MICROPYTHON;
45+
}
46+
47+
static claw_err_t tool_run_script_validate(struct claw_tool *tool,
48+
const cJSON *params)
49+
{
50+
const cJSON *code;
51+
const cJSON *language;
52+
53+
(void)tool;
54+
if (!params) {
55+
return CLAW_ERR_INVALID;
56+
}
57+
58+
code = cJSON_GetObjectItem(params, "code");
59+
language = cJSON_GetObjectItem(params, "language");
60+
61+
if (!code || !cJSON_IsString(code)) {
62+
return CLAW_ERR_INVALID;
63+
}
64+
65+
if (language && !cJSON_IsString(language)) {
66+
return CLAW_ERR_INVALID;
67+
}
68+
69+
return CLAW_OK;
70+
}
71+
72+
static claw_err_t tool_run_script_execute(struct claw_tool *tool,
73+
const cJSON *params,
74+
cJSON *result)
75+
{
76+
const cJSON *code = cJSON_GetObjectItem(params, "code");
77+
const char *language = script_language_from_params(params);
78+
char *output;
79+
80+
(void)tool;
81+
output = claw_malloc(SCRIPT_OUTPUT_MAX);
82+
if (!output) {
83+
cJSON_AddStringToObject(result, "error", "out of memory");
84+
return CLAW_OK;
85+
}
86+
output[0] = '\0';
87+
88+
if (!claw_platform_script_supported(language)) {
89+
char msg[64];
90+
91+
snprintf(msg, sizeof(msg), "runtime '%s' not supported", language);
92+
cJSON_AddStringToObject(result, "error", msg);
93+
claw_free(output);
94+
return CLAW_OK;
95+
}
96+
97+
if (claw_platform_run_script(language, code->valuestring,
98+
output, SCRIPT_OUTPUT_MAX) != 0) {
99+
cJSON_AddStringToObject(result, "error",
100+
output[0] ? output
101+
: "script execution failed");
102+
claw_free(output);
103+
return CLAW_OK;
104+
}
105+
106+
cJSON_AddStringToObject(result, "status", "ok");
107+
cJSON_AddStringToObject(result, "language", language);
108+
cJSON_AddStringToObject(result, "output", output);
109+
if (strlen(output) >= SCRIPT_OUTPUT_MAX - 1) {
110+
cJSON_AddBoolToObject(result, "truncated", 1);
111+
}
112+
113+
claw_free(output);
114+
return CLAW_OK;
115+
}
116+
117+
static const char schema_run_script[] =
118+
"{\"type\":\"object\","
119+
"\"properties\":{"
120+
"\"language\":{\"type\":\"string\","
121+
"\"description\":\"Scripting runtime. Defaults to micropython.\"},"
122+
"\"code\":{\"type\":\"string\","
123+
"\"description\":\"Script source code to execute on-device.\"}},"
124+
"\"required\":[\"code\"]}";
125+
126+
static const struct claw_tool_ops run_script_ops = {
127+
.execute = tool_run_script_execute,
128+
.validate_params = tool_run_script_validate,
129+
};
130+
131+
static struct claw_tool run_script_tool = {
132+
.name = "run_script",
133+
.description =
134+
"Execute a short on-device script and return stdout or "
135+
"exception output. RT-Thread uses MicroPython, Linux uses Python.",
136+
.input_schema_json = schema_run_script,
137+
.ops = &run_script_ops,
138+
.flags = CLAW_TOOL_LOCAL_ONLY,
139+
};
140+
141+
CLAW_TOOL_REGISTER(run_script, &run_script_tool);

include/platform/scripting.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2026, Chao Liu <chao.liu.zevorn@gmail.com>
3+
* SPDX-License-Identifier: MIT
4+
*
5+
* Platform hooks for embedded script execution.
6+
*/
7+
8+
#ifndef PLATFORM_SCRIPTING_H
9+
#define PLATFORM_SCRIPTING_H
10+
11+
#include <stddef.h>
12+
13+
/*
14+
* Return 1 when the given runtime is available on this platform.
15+
* Current users pass "micropython".
16+
*/
17+
int claw_platform_script_supported(const char *language);
18+
19+
/*
20+
* Execute script source with the selected runtime.
21+
* Returns 0 on success, -1 on failure.
22+
* output receives stdout / exception text when available.
23+
*/
24+
int claw_platform_run_script(const char *language, const char *code,
25+
char *output, size_t output_size);
26+
27+
#endif /* PLATFORM_SCRIPTING_H */

meson_options.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ option('tool_sched', type: 'boolean', value: true,
2424
description: 'Enable scheduler tool')
2525
option('tool_net', type: 'boolean', value: true,
2626
description: 'Enable network (HTTP) tool')
27+
option('tool_script', type: 'boolean', value: false,
28+
description: 'Enable embedded script execution tool')
2729
option('tool_mouse', type: 'boolean', value: false,
2830
description: 'Enable USB HID mouse tools (ESP32-S3 only)')
2931
option('heartbeat', type: 'boolean', value: false,

platform/linux/board.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,68 @@
77

88
#include "platform/board.h"
99

10+
#ifdef CONFIG_RTCLAW_TOOL_SCRIPT
11+
#include "platform/scripting.h"
12+
13+
#include <stdio.h>
14+
#include <string.h>
15+
16+
#define PYTHON_CODE_SIZE 256
17+
#define PYTHON_OUTPUT_SIZE 1024
18+
19+
static void cmd_python(int argc, char **argv)
20+
{
21+
char code[PYTHON_CODE_SIZE];
22+
char output[PYTHON_OUTPUT_SIZE];
23+
int off = 0;
24+
25+
if (argc < 2) {
26+
claw_printf("Usage: /python <code>\n");
27+
return;
28+
}
29+
30+
code[0] = '\0';
31+
for (int i = 1; i < argc && off < (int)sizeof(code) - 1; i++) {
32+
off += snprintf(code + off, sizeof(code) - off,
33+
"%s%s", i > 1 ? " " : "", argv[i]);
34+
if (off >= (int)sizeof(code)) {
35+
code[sizeof(code) - 1] = '\0';
36+
break;
37+
}
38+
}
39+
40+
if (claw_platform_run_script("python", code,
41+
output, sizeof(output)) != 0) {
42+
claw_printf("%s\n",
43+
output[0] ? output : "Python execution failed");
44+
return;
45+
}
46+
47+
if (output[0] != '\0') {
48+
claw_printf("%s", output);
49+
if (output[strlen(output) - 1] != '\n') {
50+
claw_printf("\n");
51+
}
52+
}
53+
}
54+
55+
static const shell_cmd_t s_linux_commands[] = {
56+
SHELL_CMD("/python", cmd_python, "Execute a short Python snippet"),
57+
};
58+
#endif
59+
1060
void board_early_init(void)
1161
{
1262
/* No hardware init needed on Linux */
1363
}
1464

1565
const shell_cmd_t *board_platform_commands(int *count)
1666
{
67+
#ifdef CONFIG_RTCLAW_TOOL_SCRIPT
68+
*count = SHELL_CMD_COUNT(s_linux_commands);
69+
return s_linux_commands;
70+
#else
1771
*count = 0;
1872
return NULL;
73+
#endif
1974
}

platform/linux/meson.build

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,38 @@ platform_src = files(
88
'ota_stub.c',
99
)
1010

11+
if get_option('tool_script')
12+
platform_src += files('scripting.c')
13+
endif
14+
1115
thread_dep = dependency('threads')
16+
platform_c_args = platform_defs
17+
18+
if get_option('tool_script')
19+
platform_c_args += '-DCONFIG_RTCLAW_TOOL_SCRIPT'
20+
endif
1221

1322
rtclaw_exe = executable('rtclaw',
1423
platform_src,
1524
include_directories: [claw_inc, claw_root_inc],
16-
c_args: platform_defs,
25+
c_args: platform_c_args,
1726
dependencies: [rtclaw_dep, osal_dep, cjson_dep, thread_dep],
1827
install: false,
1928
)
2029

2130
# Unit test executable — links ota_stub.c for OTA symbol resolution
2231
test_main_src = files('../../tests/unit/test_main_linux.c')
2332
test_incs = include_directories('../../tests/unit')
33+
test_platform_src = files('ota_stub.c')
34+
35+
if get_option('tool_script')
36+
test_platform_src += files('scripting.c')
37+
endif
2438

2539
rtclaw_test_exe = executable('rtclaw_test',
26-
test_main_src + files('ota_stub.c'),
40+
test_main_src + test_platform_src,
2741
include_directories: [claw_inc, claw_root_inc, test_incs],
28-
c_args: platform_defs,
42+
c_args: platform_c_args,
2943
link_with: [unittest_lib],
3044
dependencies: [rtclaw_dep, osal_dep, cjson_dep, thread_dep],
3145
install: false,

0 commit comments

Comments
 (0)