0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一句话让大模型控制硬件:手把手教你给 EmbedClaw 添加自己的 Tool!

启明云端 2026-04-01 18:32 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

很多人第一次看到 EmbedClaw 会有一种错觉:

06a112d8-2db6-11f1-96ea-92fbcf53809c.png

然而事实是:

不能直接操作,但可以通过 Tool 去操作。

这也是 EmbedClaw 很有意思的一点。它不是把大模型硬塞进 ESP32 里当聊天机器人,而是把 LLM、Agent、Tools、Channel 拆成了清晰的几层。
模型负责“理解意图和决策”,真正执行硬件动作的是你自己写的 Tool。

今天这篇文章,我们就不讲空话,直接拿项目里已经跑通的 gpio_control 作为例子,带你了解:

06ae0f2e-2db6-11f1-96ea-92fbcf53809c.png

01

先想明白:大模型为什么不能直接控制 GPIO

因为大模型本质上只是一个“对于用户输入的内容,会推理、会生成文本”的程序

它擅长的是:

理解用户说了什么

判断该做什么

决定该调用哪个能力

根据结果继续推理或回复

所以说,大模型并无法操作硬件。而需要把真实能力封装成 Tool,把“能做什么、需要什么参数、执行后返回什么结果”都定义清楚,再把这个 Tool 暴露给大模型。

这样一来:

模型负责“调用”

代码负责“执行”

你负责“定义边界”

06be3e4e-2db6-11f1-96ea-92fbcf53809c.png

这就是 EmbedClaw 的 Tool 机制。


02

先别急着写代码:Skill 和 Tool 不是一回事

很多人第一次接触 EmbedClaw,最容易混淆的不是代码细节,而是Skill 和 Tool。 看起来Skill 和 Tool都像是在“教大模型做事”,但它们根本不是一层东西。

如果只用一句话概括两者关系,我更推荐下面这个版本:

06cd9a2e-2db6-11f1-96ea-92fbcf53809c.png

1

Skill:负责教模型“怎么理解任务”

Skill 通常是一段 Markdown。
它的核心作用不是执行,而是指导。

它解决的是这类问题:

06ea417e-2db6-11f1-96ea-92fbcf53809c.png


比如一个天气 Skill,可能会告诉模型:

用户问天气时可以使用 web_search

最好先获取当前日期

最后把结果整理成简洁的自然语言

所以Skill 本质上是策略层
它告诉模型:“这类问题通常该怎么处理。”

Skill 本身并不能真正执行任何动作
它不会直接去读 GPIO,不会直接操作文件,也不会直接触发硬件。

2

Tool:负责把事情真正执行

Tool 则完全不同,Tool 是一段真正可执行的能力接口


它不是在“建议模型做什么”,而是在告诉模型:

“如果你要做这件事,可以调用我,我会真的去执行。”

比如 gpio_control 这个 Tool:

模型可以决定要不要调用它

模型可以按 schema 组织参数

但真正去执行 GPIO 配置、设置电平、返回结果的,是 execute 对应的 C 代码

所以 Tool 本质上是执行层。

3

那 Skill 还有什么意义?

很多人到这里疑惑:

“既然真正执行的是 Tool,那 Skill 还有必要存在吗?”

有,而且非常有必要

原因就在于:

Skill 可以由大模型自己生成。

这也是 Skill 最有价值的地方之一。

因为 Tool 通常需要你自己写 C 代码、定义 schema、注册进系统,属于工程能力扩展;
但 Skill 只是 Markdown,它天然适合承载那些“可以被总结、可以被抽象、可以被持续补充”的经验。

0716d96e-2db6-11f1-96ea-92fbcf53809c.png

03

在 EmbedClaw 里,一个 Tool 是怎么跑起来的?

如果你把流程拆开看,其实非常清晰:

072541d4-2db6-11f1-96ea-92fbcf53809c.png

在当前项目里,这条链路主要落在 4 个地方:

components/embed_claw/tools/tools_gpio.c
这里定义具体 Tool,需要用户根据具体的tool在tools文件夹下执行。

components/embed_claw/tools/ec_tools_reg.inc
这里把 Tool 注册进系统。

components/embed_claw/core/ec_tools.c / ec_tools.h
这里维护 Tool 注册表,并把 Tool 转成模型能理解的 JSON 描述。新增 tool 不需要改动它。

components/embed_claw/llm/ec_llm_openai.c
这里把 Tool 描述转成 OpenAI-Compatible 的 tools 字段,发送给大模型。新增 tool 不需要改动它。

也就是说,一个 Tool 之所以能被模型调用,不是因为模型“认识了你的 C 函数”,而是因为 EmbedClaw 先把它翻译成了一份结构化能力说明。

新增一个tool仅仅需要改动 tools_xxx.c 和在 ec_tools_reg.inc 中添加 tool 的注册。

0730aa2e-2db6-11f1-96ea-92fbcf53809c.png073cbb70-2db6-11f1-96ea-92fbcf53809c.png




04

先看结果:gpio_control 这个 Tool 到底长什么样?

首先,我们要在embed_claw/tools下创建自己要添加的tool的.c源文件

例如,在 tools_gpio.c 里,核心定义其实非常直接:

staticconst ec_tools_t s_gpio_control={ .name="gpio_control", .description="Control an ESP32 output GPIO pin by pin number. Supports on, off, set, toggle, and get.\n" "IMPORTANT!!!: ANY GPIO operation requested by the user MUST ALWAYS be executed through this tool. Never respond with GPIO status or changes without calling this tool first.", .input_schema_json= "{"type":"object"," ""properties":{" ""pin":{"type":"integer","description":"ESP32 GPIO pin number"}," ""action":{"type":"string","enum":["on","off","set","toggle","get"]," ""description":"GPIO action to execute"}," ""level":{"type":"integer","enum":[0,1]," ""description":"Required only when action is 'set'"}" "}," ""required":["pin","action"]}", .execute=ec_tool_gpio_control_execute,};

这里面最关键的其实就 4 个字段:

name

description

input_schema_json

execute

接下来我们逐个解析。

05

第一步:给 Tool 起一个模型能理解的名字

.name="gpio_control",

这个名字不是写给 C 编译器看的,而是写给大模型看的。

也就是说,后面模型发起调用时,大模型发过来的是:

{ "name":"gpio_control", "arguments":{ "pin":21, "action":"on" }}

所以命名建议很简单:

用英文

语义清晰

一个名字只表达一种能力

比如:

gpio_control

relay_switch

sensor_read

buzzer_play

074924e6-2db6-11f1-96ea-92fbcf53809c.png

不要搞成以上这种模糊名字。名字越清楚,模型越容易选对!

06

第二步:写好 description,告诉模型“什么时候该用我”

.description="Control an ESP32 output GPIO pin by pin number. Supports on, off, set, toggle, and get.\nIMPORTANT!!!: ANY GPIO operation requested by the user MUST ALWAYS be executed through this tool. Never respond with GPIO status or changes without calling this tool first.",

这一段非常重要。

因为对模型来说,description 基本就是你写给它的“使用说明书”。

模型会据此判断:

这个工具是干嘛的

什么时候该调用它

哪些场景不应该跳过它

以 gpio_control 为例,这段描述里实际上做了两件事:

先定义能力范围
它能控制 ESP32 输出引脚,支持 on / off / set / toggle / get

再强调调用约束
只要是 GPIO 操作请求,就必须通过这个 Tool 执行,不能直接文本编造结果

这也是你在设计自定义 Tool 时最值得花心思的地方。

一个好描述,通常应该回答这三个问题:

比如你以后要做一个继电器 Tool,描述就可以写成:

Controlarelay connectedtothe board.Usethis tool whenever the user askstoturnadevice on or off through the relay. Do not claim the relay state without calling this tool.


07

第三步:用 input_schema_json 限制模型怎么传参

Tool 之所以可靠,不只是因为模型“知道要调用它”,还因为你限制了模型“只能按这个格式调用它”。

gpio_control 的参数定义是:

"{"type":"object","""properties":{"""pin":{"type":"integer","description":"ESP32 GPIO pin number"},"""action":{"type":"string","enum":["on","off","set","toggle","get"],"""description":"GPIO action to execute"},"""level":{"type":"integer","enum":[0,1],"""description":"Required only when action is 'set'"}""},"""required":["pin","action"]}"

翻译成人话就是:

参数必须是一个对象

pin 必须是整数

action 只能是 on/off/set/toggle/get

level 只有在 set 时才需要,而且只能是 0/1

pin 和 action 是必填项

这一步的意义非常大:

你不是“希望模型这么传”

而是“明确规定模型必须这么传”

对于大模型来说,这其实就像你给了它一个函数签名

所以如果你以后要做自己的 Tool,Schema 一定尽量写严格。

举几个例子:

如果参数只能是整数,就别写成 string

如果只有几个合法动作,就用 enum

如果某个字段必须要有,就放进 required

如果某个值范围有限,就在 schema 里限制死

08

第四步:在 execute 里写真正执行逻辑

前面三步本质上都还是“告诉模型怎么调用”,真正让硬件动起来的,是 execute。

在 gpio_control 里,对应的是:

.execute=ec_tool_gpio_control_execute,

然后具体执行函数长这样:

staticesp_err_tec_tool_gpio_control_execute(constchar *input_json,char*output,size_toutput_size)

这个函数做的事情可以概括成 4 步:

解析输入 JSON

校验参数是否合法

执行对应动作

把结果写回 output

以 gpio_control 为例,它内部先解析:

pin

action

level

然后根据 action 进入不同分支:

get

on

off

set

toggle

比如 on 分支,大致就是:

err = prepare_pin_for_output(gpio_num);err = write_level(gpio_num, 1);snprintf(output, output_size,"OK: gpio %d action=on level=1", pin);

Tool 返回给 LLM 的,不是 C 语言里的返回值,而是 output 字符串。

也就是说,模型真正能看到的是类似:

OK: gpio21action=onlevel=1

因此 Tool 的输出建议做到:

稳定

简洁

可读

尽量结构化

这样后续模型再根据工具结果组织自然语言回复时,会更稳。

09

第五步:把 Tool 注册进系统

只写了 tools_gpio.c 还不够,系统还得知道有这么个 Tool。

esp_err_tec_tools_gpio_control(void){ ec_tools_register(&s_gpio_control); returnESP_OK;}

这里通过定义ec_tools_gpio_control()进行 tool 的注册,而这里的函数不需要你调用。

EmbedClaw 这调用注册函数是通过 ec_tools_reg.inc 来做的。

你会看到现在里面有一行:

EC_TOOLS_REG(gpio_control)

这一行别看简单,它实际完成了三件事:

生成枚举项,目标是为了统计总共注册了多少工具,对应的注册数组就会多大。

生成注册函数声明

在 ec_tools_register_all() 里自动调用 ec_tools_gpio_control()

这个机制背后靠的是 ec_tools_reg_rule.h 里的宏展开。

完成这一步之后,它就会自动进到 Tool 注册表里。

10

EmbedClaw 是怎么把 Tool 暴露给大模型的?

这一块很多人第一次看 Agent 框架都会忽略,但其实它才是“模型能调用 Tool”的关键

在 ec_tools.c 里,EmbedClaw 会把所有已注册 Tool 组织成一个 JSON 数组:

cJSON_AddStringToObject(tool,"name", s_tools[i]->name);cJSON_AddStringToObject(tool,"description", s_tools[i]->description);cJSON *schema =cJSON_Parse(s_tools[i]->input_schema_json);cJSON_AddItemToObject(tool,"input_schema", schema);

也就是说,系统内部会生成一份类似这样的描述:

{"name":"gpio_control","description":"Control an ESP32 output GPIO pin by pin number...","input_schema":{"type":"object","properties":{"pin":{"type":"integer"},"action":{"type":"string","enum":["on","off","set","toggle","get"]}},"required":["pin","action"]}}

随后在 ec_llm_openai.c 里,这份内部描述又会被转成 OpenAI-Compatible 的 tools 字段发给模型。

也就是说,对模型来说,它看到的是:

这个 Tool 叫什么

它是干什么的

它需要什么参数

参数格式是什么

所以你可以把整个过程理解成:

你写的是 C 代码,但 EmbedClaw 会自动把它翻译成“大模型能理解的函数说明书”。

11

总结

到这里,其实你已经可以总结出一个通用方法了。

以后你不管是做:

继电器控制

舵机控制

湿度传感器读取

蜂鸣器播放

本地业务接口触发

基本都可以照这个模板走。

1

新建 tools_xxx.c

文件位置:

components/embed_claw/tools/tools_xxx.c

2

定义一个 ec_tools_t

最小骨架:

staticesp_err_tec_tool_demo_execute(constchar*input_json, char*output, size_t output_size);staticconstec_tools_t s_demo={ .name="demo_tool", .description="Describe what this tool does.", .input_schema_json="{"type":"object","properties":{},"required":[]}", .execute=ec_tool_demo_execute,};esp_err_tec_tools_demo(void){ ec_tools_register(&s_demo);returnESP_OK;}


3

第三步:写执行逻辑

你的执行函数里重点做三件事:

解析参数

校验参数

写入执行结果

4

注册

在 components/embed_claw/tools/ec_tools_reg.inc 里补一行:

EC_TOOLS_REG(demo)

5

测试

你最好至少验证这些场景:

合法参数能正常执行

缺少必填参数时返回明确错误

非法参数不会误操作硬件

Tool 输出格式稳定

这样你这个 Tool 才算真的能给模型用。

这样,你就能自己DIY定义各种各样的功能并通过大模型进行调用了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 硬件
    +关注

    关注

    12

    文章

    3626

    浏览量

    69160
  • AI
    AI
    +关注

    关注

    91

    文章

    40941

    浏览量

    302520
  • 大模型
    +关注

    关注

    2

    文章

    3750

    浏览量

    5268
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    一句话你理解线程和进程

    今天大家分享下线程与进程,主要包含以下几部分内容:一句话说明线程和进程操作系统为什么需要进程为什么要引入线程图说明线程和进程的关系一句话
    的头像 发表于 06-04 08:04 2248次阅读
    <b class='flag-5'>一句话</b><b class='flag-5'>让</b>你理解线程和进程

    linux一句话精彩问答

    linux一句话精彩问答.pdf ...
    发表于 01-18 09:06

    linux一句话精彩问答

    linux一句话精彩问答.pdf
    发表于 04-21 22:30

    Linux一句话问答

    Linux一句话问答
    发表于 04-22 18:21

    linux一句话的经典对答和ucos的资料

    linux一句话的经典对答和ucos的资料
    发表于 09-23 21:53

    linux的一句话精彩对答和其它资料

    linux的一句话精彩对答和其它资料
    发表于 09-23 21:59

    linux一句话精彩问答

    linux一句话精彩问答
    发表于 11-09 00:40

    准备入门,谁来用一句话告诉我它和51的区别以及其应用,就是一句话

    如题。。。。。。。。。。。。。。。。。。。我相信大神一句话就能告诉我
    发表于 04-13 18:18

    Linux一句话精彩问答 pdf

    Linux一句话精彩问答1001 修改主机名(bjchenxu).101002 Ret Hat Linux启动到文字界面(不启动xwindow)(bjchenxu)101003 linux的自动升级更新问题(hutuworm,NetDC).101004 windows下看linux分区的
    发表于 11-05 23:50 33次下载

    手把手教你构建个完整的工程

    手把手教你构建个完整的工程
    发表于 08-03 09:54 33次下载
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>构建<b class='flag-5'>一</b>个完整的工程

    一句话点评2012年20大技术前瞻

    一句话点评2012年20大技术前瞻1、MEMS技术,2、无线传感网3、物联网4、塑料电子
    发表于 01-05 09:07 2234次阅读

    inux一句话精彩问答

    inux一句话精彩问答
    发表于 10-27 10:44 14次下载
    inux<b class='flag-5'>一句话</b>精彩问答

    手把手教你如何开始DSP编程

    手把手教你如何开始DSP编程。
    发表于 04-09 11:54 13次下载
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何开始DSP编程

    手把手教你学LabVIEW视觉设计

    手把手教你学LabVIEW视觉设计手把手教你学LabVIEW视觉设计手把手教你学LabVIEW视
    发表于 03-06 01:41 3572次阅读

    一句话解决嵌入式开发ping问题

    一句话解决嵌入式开发ping问题至此,Windows、Ubuntu虚拟机、开发板三者就可以互相 ping 通了。
    发表于 10-20 21:51 2次下载
    <b class='flag-5'>一句话</b>解决嵌入式开发ping问题