文章目录
- 授勋流程设计
- 开发环境与实验条件
- 项目结构与构建流程
- 配置、模板与构建脚本
- 配置校验与代码生成
- 技能包结构
- 剧情与灯效配置
- SDK 接口与调用限制
- 动作编号、状态检查与重试
- TTS 请求与异步播报
- LED 模式与参数
- 表情编号与请求参数
- 请求顺序与执行时序
- 技能打包与离线预览
- PrimeConsole 导入与仿真结果
- 写在最后
大家好,我是颜颜yan_。
最近我在想,生活里有很多值得被肯定的小事。比如按时起床,完成一件拖延很久的任务,或者在不太顺利的一天里,坚持把事情做完。
这些努力常常悄悄发生,也很少得到掌声。
如果这时候有一个机器人向你招招手,认真地说一句“今天的你,值得一个大大的赞”,会不会开心一整天。
带着这个想法,我做了小事授勋官。
我为它安排了一场小小的授勋仪式。先招手邀请你,再为你的努力加油,接着双手比心,送出一枚**“平凡生活闪光者勋章”**,最后用庆祝动作收尾。
仪式在脑海里已经有了画面,到了动手这一步,才要面对一个实际问题——我手头没有实体 Q1。
那就先从电脑上开始。我用 Windows 编写配置和代码,对照 Q1 SDK 核对接口,再把它打包成 PrimeConsole 可以导入的技能包。
授勋流程设计
我想通过“小事授勋官”表达的,是小事,也值得郑重其事。
围绕这个想法,我安排了招手邀请、努力认证、颁发勋章和共同庆祝四个环节。
我最想保留的是颁勋时双手比心,配上那句“小事,也值得郑重其事”。前面的邀请和加油,都是在为这一刻做准备。
每个环节都安排了对应的动作和台词,再配上开心表情与灯效,让邀请、肯定和庆祝有各自的表达。
| 环节 | 动作与编号 | 配套台词 | 灯效设计 |
|---|---|---|---|
| 授勋邀请 | 招手,3001 | 这里是 Malyu 小事授勋局,请接受今天的授勋。 | 暖金呼吸 |
| 努力认证 | 加油,3008 | 经本局认证,今天的你,值得一个大大的赞。 | 金色闪烁 |
| 颁发勋章 | 双手比心,3007 | 现授予你,平凡生活闪光者勋章。小事,也值得郑重其事。 | 粉色呼吸 |
| 庆祝时刻 | 跳舞动作 A,3046 | 授勋完成!请带着这份肯定,继续闪闪发光。 | 彩虹 |
这四个环节组成了当前版本的完整流程。
程序按顺序提交动作、台词、表情和灯效请求,台词使用表中预先写好的内容。
流程确定后,下一步就是准备开发环境,把这些设计落实到配置和代码中。
开发环境与实验条件
要在电脑上看到这段动作,得先把剧情做成技能包。
我把这次实践分为本地构建和模拟仿真两部分,先确认手头的工具能完成哪些工作。
下面是本次用到的工具和资料,也列出了后续真机联调需要的环境,有需要的小伙伴也可以参考呀。
| 软件或资料 | 在本次实验中的用途 | 当前情况 |
|---|---|---|
| Windows 与 PowerShell | 管理项目、执行打包与离线预览 | 已使用 |
Python 3.12.9 与项目.venv | 读取 JSON、生成 Python 和 YAML、封装.rpk | 已使用 |
| 代码编辑器,例如 PyCharm | 编辑配置、模板与生成器,查看生成代码 | 可使用现有编辑器 |
| Q1 SDK v1.0.0.0 | 核对动作、表情、灯带、TTS 和运行环境要求 | 已用于接口核对 |
| PrimeConsole | 导入技能包、匹配预设动作、查看仿真表现 | 当前测试环境可呈现上肢动作 |
| Ubuntu 22.04 与 ROS 2 Humble | 构建 SDK 消息并连接 Q1 服务 | 真机联调环境,本次未验证 |
| 截图或录屏工具 | 记录配置、代码、打包结果和仿真画面 | 用于补充本文配图 |
本地构建可以先做起来。
打包脚本只使用argparse、json、shutil、zipfile和pathlib等 Python 标准库,手头的 Windows 环境就能生成.rpk、打印离线清单,可以先动手把剧情写进去。
后续连接实体 Q1、通过 SDK 调用机器人服务时,还需要准备相应的运行环境。
推荐在 Ubuntu 22.04 + ROS2 Humble 环境下进行开发,暂不支持在 Mac、Windows 系统下进行开发。
除开发 PC 外,机器人自带的运控板与大脑板均支持直接进行二次开发。
按照这个范围,这次我先在 Windows 上完成配置编写、代码生成、技能打包和仿真观察。
等到真机联调时,再按 SDK 的要求准备运行环境,验证服务调用与实际表现。
项目结构与构建流程
有了开发环境,我开始把前面那张授勋流程表写进程序。
比如“颁发勋章”这一段,需要把双手比心、第三句台词和粉色呼吸灯效对应起来,其他三个环节也要各自配好。于是,我把这些内容按环节放进 JSON,保留和流程表相同的顺序,方便逐段核对。
接着要处理的是,这些配置怎样变成实际调用。
四个环节都会用到动作、灯效、表情和播报接口,我把这部分调用逻辑放进执行模板,由 Python 脚本读取 JSON,将每段的动作编号、灯效参数和台词填进去,生成入口代码。
这样,以后想给勋章换一句台词,或者调整邀请环节的灯光,就可以回到对应的配置里修改,再重新生成。
要把它放进 PrimeConsole,还需要补上包名、作者和版本等信息。
这部分由元数据模板生成,最后和入口代码、剧情配置一起封装成q1_skin_award_officer.rpk。从流程表到可导入的技能包,几个文件就这样连了起来。
下面按这条流程介绍当前“小事授勋官”项目中的文件。
整理后的项目根目录见下图。
这里的dist/_build/award_officer/是方便检查的展开结果。
每次运行build.py时,脚本会先删除这个目录,再根据当前 JSON 重新生成它;.rpk也是从这个目录重新压缩出来的。
把这些步骤连起来,就是从源文件到 PrimeConsole 的完整流程。
1. skins/award_officer.json ↓ 读取剧情、动作、表情与灯效 2. build.py ↓ 校验 storyboard、台词索引、动作顺序和灯效参数 3. template/award_show.py.tpl + template/pkg.yaml.tpl ↓ 替换占位符,生成实际 Python 代码和 YAML 元数据 4. dist/_build/award_officer/ ├─ main.py ├─ cfg/pkg.yaml ├─ skin.json └─ README.md ↓ ZIP_DEFLATED 封装 5. dist/q1_skin_award_officer.rpk ↓ 导入、识别动作编号并匹配本地资源 6. PrimeConsole 模拟仿真配置、模板与构建脚本
| 文件 | 作用 |
|---|---|
[skins/award_officer.json](C:/Users/user/Downloads/q1-persona-skin-kit-master/skins/award_officer.json) | 唯一的业务配置。保存名称、包名、四句台词、四个动作编号、默认表情、默认灯效和四段storyboard。修改创意时优先编辑这里。 |
[template/award_show.py.tpl](C:/Users/user/Downloads/q1-persona-skin-kit-master/template/award_show.py.tpl) | 执行层模板。定义离线预览、ROS 节点、Q1 服务客户端、状态检查、动作忙碌重试、TTS/LED/表情请求和失败退出逻辑。 |
[template/pkg.yaml.tpl](C:/Users/user/Downloads/q1-persona-skin-kit-master/template/pkg.yaml.tpl) | 包描述模板。提供package_name、author、robot_model、version和sdk_version等元数据字段,其中作者固定为Malyu。 |
[build.py](C:/Users/user/Downloads/q1-persona-skin-kit-master/build.py) | 构建控制器。读取 JSON,校验配置,展开每段剧情,替换两个模板中的占位符,生成中间目录并压缩.rpk。 |
[sdk_q1-v1.0.0.0/](C:/Users/user/Downloads/q1-persona-skin-kit-master/sdk_q1-v1.0.0.0) | 能力依据。这里保存 Q1 的.srv/.msg定义、动作编号、表情编号、灯带参数和运行环境说明;它不是本项目的皮肤源代码。 |
award_officer.json负责回答“授勋官要表达什么”,award_show.py.tpl负责回答“怎样调用 SDK 表达”,而build.py负责把两者组合起来。
这样修改台词或灯效时不必直接改生成后的main.py;修改服务调用和错误处理时,也不会把剧情数据散落到代码里。
配置校验与代码生成
build.py的处理可以拆成五步:
- 读取
skins/award_officer.json,确认配置使用profile: "award_show"。 - 检查
storyboard非空,并确认其中的motion_id顺序正好是[3001, 3008, 3007, 3046]。 - 根据每段的
line_index找出台词;没有单段led时,继承顶层的暖金默认灯效。 - 把每段展开成实际调用:
set_preset_motion→set_led→set_emotion→speak。任一调用失败,生成代码就返回False,不继续下一段。 - 将渲染后的文件写入
dist/_build/award_officer/,再把该目录压缩为dist/q1_skin_award_officer.rpk。
以图中的颁勋配置为例。
构建脚本会将它展开为下面这段main.py代码。
ifnotset_preset_motion(3007):returnFalseifnotset_led(254,(255,100,150),1200):returnFalseifnotset_emotion(3003):returnFalseifnotspeak('现授予你,平凡生活闪光者勋章。小事,也值得郑重其事。'):returnFalse因此,JSON 是便于修改和校验的剧情层,生成后的main.py才是 PrimeConsole 静态扫描和 Q1 真机入口实际看到的执行层。
技能包结构
q1_skin_award_officer.rpk使用 ZIP 结构封装,当前包内有四类文件:
| 包内路径 | 来源 | 用途 |
|---|---|---|
main.py | award_show.py.tpl渲染结果 | 入口程序,包含显式动作编号和声光表情请求 |
cfg/pkg.yaml | pkg.yaml.tpl渲染结果 | PrimeConsole 读取的包名、作者、机器人型号和版本信息 |
skin.json | 原始award_officer.json的复制品 | 保留完整剧情配置,便于导入后检查 |
README.md | build.py根据当前配置生成 | 记录四段剧情、仿真限制、本地预览和真机入口 |
构建目录方便检查生成结果,最终包用于导入。
构建目录中的文件是普通文件,可以直接打开调试。
.rpk是把这些文件打包后的交付物,导入 PrimeConsole 时选择的是.rpk。
剧情与灯效配置
写配置时,我又把四个环节在脑海里过了一遍。招手邀请时,希望灯光温暖一些;到了比心颁勋,换成粉色;最后再用彩虹配合庆祝。
这些小安排,都要一项项写进配置,才有机会出现在之后的演示里。
这里的 JSON 可以理解为一份给程序看的分镜表。
我先在顶层写好默认的开心表情和暖金呼吸灯效,再按四个环节的顺序填入动作编号。这部分配置写成下面这样。
{"id":"award_officer","display_name":"小事授勋官","package_name":"q1_skin_award_officer","profile":"award_show","emotion_id":3003,"led":{"mode":254,"rgb":[255,220,160],"period":1600},"motions":[3001,3008,3007,3046]}上面展示了配置中的部分字段。完整文件还通过lines保存四句台词,通过storyboard逐段记录动作、台词的对应关系,以及各环节需要调整的灯效,使用时需要保留完整配置。
写到“颁发勋章”这一段时,我把双手比心和第三句台词配在一起,再单独设置粉色呼吸灯效。
{"stage":"颁发勋章","motion_id":3007,"line_index":2,"meaning":"双手比心,把一枚心意勋章送给你。","led":{"mode":254,"rgb":[255,100,150],"period":1200}}这里的motion_id: 3007对应双手比心,line_index: 2取出第三句授勋台词,因为台词索引从 0 开始。
这一段还单独写了led,将默认的暖金色调整为粉色。其他环节如果省略led,就沿用顶层的默认灯效。
四段内容写好后,构建脚本会先检查剧情是否为空、动作顺序是否与motions一致、台词索引是否有效,再检查 RGB、灯效模式和period的取值范围。
这样可以在打包前发现配置中的遗漏或填写错误。设备上是否有对应的动作、表情资源,以及实际播放效果如何,还需要在后续联调中逐项确认。
SDK 接口与调用限制
写分镜时,“招手之后说一句话,再接着比心”是一句很自然的描述。
落到程序里,还需要逐个确认。动作能不能接着发?台词请求返回时,机器人是不是已经说完?灯光参数里的时间,又到底表示什么?
尤其是颁勋那句话,我希望它能和比心动作配合好。为了把这些细节安排清楚,我对照本地.srv文件和《接口说明》,逐项核对动作、声音、灯光和表情的调用方式。下面保留了这次用到的接口与代码,方便有需要的小伙伴参考。
| 能力 | ROS 2 服务名 | 当前用途 |
|---|---|---|
| 预设动作 | /aimdk_5Fmsgs/srv/SetMcPresetMotion | 招手、加油、比心、庆祝 |
| 状态查询 | /aimdk_5Fmsgs/srv/GetMcAction | 真机入口开始前检查 Q1 状态 |
| 文本播报 | /aimdk_5Fmsgs/srv/PlayTts | 提交四句授勋台词 |
| 灯带控制 | /aimdk_5Fmsgs/srv/LedStripCommand | 提交四段灯效配置 |
| 表情播放 | /aimdk_5Fmsgs/srv/PlayEmotion | 请求开心表情 3003 |
动作编号、状态检查与重试
我先在 Q1《接口说明》的动作表里核对了授勋流程用到的四个动作,编号和名称如下:
| SDK 动作编号 | SDK 描述 |
|---|---|
| 3001 | 招手 |
| 3008 | 加油 |
| 3007 | 双手比心 |
| 3046 | 跳舞动作A |
确定动作编号后,接着看怎样把它填进请求。预设动作使用SetMcPresetMotion接口,下面节选了.srv文件中的字段:
RequestHeader header string source McControlArea area McPresetMotion motion bool interrupt string ani_path --- CommonTaskResponse response按照这个结构,我在真机入口代码中填入动作编号,并将interrupt设为False,让后续请求保留前一个动作的执行过程。
这部分请求构造如下:
request=SetMcPresetMotion.Request()request.header.stamp=NODE.get_clock().now().to_msg()request.source="sdk_rpc"request.motion.value=motion_id request.interrupt=Falserequest.ani_path=""发送请求前,程序会先按 SDK 的要求,确认机器人处于BIPED_WHOLE_BODY_CTRL,并通过状态反馈确认RUNNING。当前程序会在条件满足后继续执行;条件尚未满足时就退出,留待准备好控制状态后再运行。
机器人状态满足要求后,还要考虑前后两个动作的衔接。前面设置了interrupt=False,如果上一个动作仍在执行,新请求就可能被拒绝。程序会根据返回的错误码处理。遇到REASON_TASK_RUNNING,就在限定时间内重试;遇到其他拒绝或超时,则停止后续请求。
这里需要留意,被拒绝的请求也可能返回state=RUNNING。因此,代码会结合错误码判断这次请求是否被接受,避免把机器人正在运行的状态误读为新动作已经开始。
除了动作能否接续执行,我还核对了请求中的area字段,确认它对运动范围有什么影响。
按照 SDK 的说明,
area控制掩码仅供上层标识参考,系统根据动作文件中提取出的曲线驱动电机,运动范围由对应的动作资源决定。
TTS 请求与异步播报
动作请求安排好后,接下来是让 Q1 说出每个环节的授勋台词。
我用到的是PlayTts接口,它在PlayTts.srv中定义的请求与响应结构如下:
CommonRequest header PlayTtsRequest tts_req --- CommonResponse header PlayTtsResponse tts_resp按照这个结构,我先创建PlayTts.Request(),再把台词和播报参数填进其中的tts_req。
当前代码中的请求构造如下:
request=PlayTts.Request()request.header.header.stamp=NODE.get_clock().now().to_msg()request.tts_req.text=text request.tts_req.priority_level.value=TtsPriorityLevel.INTERACTION_L6 request.tts_req.priority_weight=0request.tts_req.domain="q1_skin_award_officer"request.tts_req.trace_id=""request.tts_req.is_interrupted=False这段代码用于真机入口,需要先加载 ROS 2 和 SDK,并创建节点,才能构造和发送请求。本次在 Windows 上完成了这部分代码编写,实际播报留到真机联调时验证。
台词提交后,还要考虑它与动作怎样衔接。
PlayTts使用异步调度。服务返回is_success: true时,播报请求已经进入队列,声音会由调度系统安排播放。台词何时说完,还需要单独判断。
LED 模式与参数
台词之外,我还给四个环节配了不同的灯效。
邀请时用暖金呼吸,认证时用金色闪烁,颁勋时换成粉色呼吸,最后用彩虹配合庆祝。
这些设计通过LedStripCommand接口提交,相关字段和模式常量在LedStripCommand.srv中定义如下:
CommonRequest request uint8 led_strip_mode uint8 LED_RAINBOW = 2 uint8 LED_CUSTOM_BREATH = 254 uint8 LED_CUSTOM_BLINK = 255 uint8 r uint8 g uint8 b uint16 period --- CommonResponse header对应到配置中,每段需要填入灯效模式、RGB 颜色和时间参数period。四个环节的取值如下:
| 环节 | mode | RGB | period |
|---|---|---|---|
| 授勋邀请 | 254,自定义呼吸 | 255、220、160 | 1600 ms |
| 努力认证 | 255,自定义闪烁 | 255、180、0 | 500 ms |
| 颁发勋章 | 254,自定义呼吸 | 255、100、150 | 1200 ms |
| 庆祝时刻 | 2,内置彩虹 | 0、0、0,占位值 | 0,占位值 |
这里的period要结合灯效模式来理解。
邀请和颁勋使用自定义呼吸模式,1600 ms 和 1200 ms 分别表示灯光从最亮到熄灭的时间;认证环节使用自定义闪烁模式,500 ms 表示点亮或熄灭各自的保持时间。
到了庆祝环节,我选用内置彩虹模式,颜色变化由该模式处理。
配置中的 RGB 和period都填为 0 作为占位值,接口会忽略这些自定义参数,按内置彩虹效果执行。
表情编号与请求参数
配合授勋时的肯定和庆祝,我给四个环节都选了开心表情。
对照 Q1 的 Emotion 表,这个表情的编号是 3003。查编号时需要留意所属的资源表。表情表中的 3003 是“开心”,动作表中的 3003 则是“碰拳”,调用时要配合对应的服务使用。
确定表情后,我在PlayEmotion请求中填写type="emotion"、emotion_ids=[3003]和loop_count=1,请求播放一次开心表情。
请求顺序与执行时序
接口核对好后,我按剧情串起各项请求。以开场的授勋邀请为例:
# 1. 授勋邀请:招手邀请你接受今天的小事勋章。ifnotset_preset_motion(3001):returnFalseifnotset_led(254,(255,220,160),1600):returnFalseifnotset_emotion(3003):returnFalseifnotspeak('这里是 Malyu 小事授勋局,请接受今天的授勋。'):returnFalse四个环节都按**“动作 → 灯效 → 表情 → 台词”**的顺序提交请求。动作编号直接写入生成代码,方便核对和 PrimeConsole 导入匹配。
程序逐项检查返回结果,**任何一个必要请求失败,就停止后续流程。**TTS 还会检查tts_resp.is_success。
程序通过参数选择离线预览或真机调用,直接运行则显示帮助:
| 入口 | 实际行为 | 是否调用机器人 |
|---|---|---|
| 不带参数 | 显示帮助 | 否 |
--preview | 打印动作、灯效、表情和台词清单 | 否 |
--robot | 加载 ROS 2,检查状态并尝试调用 Q1 服务 | 是,尚未实机验证 |
技能打包与离线预览
配置和模板准备好后,我在项目根目录执行打包命令:
.\.venv\Scripts\python.exe build.py也可以显式指定授勋官配置,两条命令生成的技能包相同:
.\.venv\Scripts\python.exe build.py--skin award_officer构建完成,目录里有了dist/q1_skin_award_officer.rpk。前面分散在配置和模板里的内容,到这里装进了同一个文件,可以带进 PrimeConsole 了。
导入前,我核对了包内文件和元数据。包名为q1_skin_award_officer,作者为Malyu,机器人型号为q1。
版本信息也一并记录下来。SDK 文件夹标为v1.0.0.0,包元数据的sdk_version为v1.0.0。后续联调时,还需要确认它们的兼容情况。
导入 PrimeConsole 前,我先运行离线预览,检查每段内容是否对应:
.\.venv\Scripts\python.exe dist\_build\award_officer\main.py--preview终端按顺序打印出四段内容,我对照流程表核对了台词和灯效参数。接下来,就可以打开仿真窗口,看看这些编号对应的动作了。
PrimeConsole 导入与仿真结果
离线预览完成后,我打开 PrimeConsole 的 Skill 页面,导入刚生成的.rpk。接下来,我按页面提示匹配本地预设资源。代码引用的3001、3008、3007、3046,分别对应招手、加油、比心和庆祝。
匹配好资源后,我启动模拟仿真。
窗口里的 Q1 动起来了。
前面写了那么多编号和参数,到这里,视线终于可以从代码移到机器人身上。想着流程表里安排的招手和比心,再看窗口中的上肢动作,授勋官的形象具体了许多。
这次记录的是上肢动作仿真,台词和灯效仍保留在程序设计中。看着这个画面,也更容易想象以后那句授勋台词配上动作的样子。
写在最后
写到这里,我让“小事授勋官”,给这次动手做点东西的自己颁一枚勋章。
没有实体机器人,就先查资料、写配置,在仿真里看看动作。原先脑海里的那个小念头,经过这些具体的小事,终于留下了一段可以回看的画面。
我们做成很多事情的过程,大概也藏在这样一个个小步骤里。
也许有人终于交出了拖了很久的作业,有人鼓起勇气发出一条消息,也有人忙了一整天,终于坐下来好好吃了顿饭。每个人心里,都有一些值得为自己鼓掌的时刻。
生活里的小事,值得我们用一点仪式感,认真收藏。
希望有一天,一个恰好需要鼓励的人,也能听到这句写在程序里的台词。
今天的你,值得一个大大的赞。
如果今天也给自己颁一枚勋章,你想奖励自己完成的哪件小事?
欢迎在评论区留下你的“小事”和勋章名字,让我们一起为彼此鼓掌。
喜欢这个小实验,也欢迎点个赞、点个“在看”,或转发给小伙伴一起瞅瞅。
欢迎大家加入启元开发者社区https://dev.primebot.com/,后续的 Q1 开发记录,我会继续和大家分享呀。