启元 T1 Pro 人机相声搭档:语音-触摸双通道的启元机器人节奏引擎设计
机器人和你说相声,它逗哏,你拍背捧哏。本文记录 crosstalk(人机相声搭档)的开发过程:TTS 没有语速参数的情况下节奏如何控、2.5 秒捧哏窗口的两条走向、以及一个在三个技能里犯了四次的 ROS2 定时器错误。全部逻辑经 --sim 验证后打包 .rpk 部署上板。
目录
-
一、选题:一个会接话的技能
-
二、演出规则:五种状态和一张表
-
三、为什么应该把段子设计为数据
-
四、在官方预设动作上二次调整
-
五、节奏控制:没有语速参数的引擎
-
六、--sim 验证:一次四犯的定时器错误
-
七、部署上板与发布
-
八、常见问题排查
-
九、已知限制
-
结语 / 参考资料
一、选题:一个会接话的技能
前两个技能分别占了数据(robot_checkup)和视觉(color_seek)两个维度,互动维度上 touch_game 已经验证了“触摸事件流 + 多模态反馈”的链路。第三个技能我想从全新的角度:让用户从“操作者”变成“表演者”。
相声这个形式几乎是为此量身定做的——它有固定的捧哏结构(逗哏说一句,捧哏接一句),接不接话是两种完全不同的走向,天然适合“机器人说、人拍背接”的交互。而且它对硬件的要求低到只需要 TTS、表情、灯带三个已经在用的通道,零运动风险。
最终玩法:机器人逗哏说原创相声,每句台词后留 2.5 秒捧哏窗口,拍背 = 接住,不拍 = 它自己吐槽,谢幕统计你的捧哏专业度。
照例交代一下地基。WSL2 + Ubuntu 22.04 + ROS2 Humble 的环境、t1-sdk 四个包的编译、URDF 模型在 RViz/Gazebo 里的加载验证,都是前两个技能留下的存量,crosstalk 开工时零环境成本:

t1-sdk 在 WSL 里 colcon build:4 packages finished——crosstalk 用到的 PlayTts、PlayEmotion、LedStripCommand 服务定义全在这个包里。

RViz 里的 T1 Pro 模型与关节列表。crosstalk 虽然不碰运动,但表情和灯带都挂在模型同一条状态链路上,模型能加载说明状态通道是通的。
更要紧的是通道级验证:TTS、表情、灯带三个接口在 touch_game 里已经实打实调过几百次,超时兜底、is_interrupted 打断了这些细节都有现成答案。crosstalk 的技术风险在动工前就只剩下“节奏怎么控”。
二、演出规则:五种状态和一张表
| 阶段 | 触摸 | 行为 |
|---|---|---|
| 待机 | 背部单击 | 开演(随机或指定段子) |
| 说词中 | 台词结束后 2.5 秒内拍背 | 接住了——计数 +1,机器人随机夸奖 |
| 说词中 | 不拍 | 它自己吐槽(“行吧,我自己来”),继续说 |
| 说词中 | 乱拍 | 忽略——防误触,不打断正在说的句子 |
| 谢幕 | 背部单击 | 返场重讲 |
| 谢幕 | 胸部双击 | 散场 |
核心机制:
crosstalk 捧哏窗口与两条走向:窗口内拍背 = 接住(计数+夸奖),窗口内不拍 = 没接(吐槽也是包袱),台词播放中乱拍直接忽略。
窗口判定全部收在 on_touch 一个函数里,按相位分流:
def on_touch(self, msg):
with self._lock:
if self.phase == PHASE_APPLAUSE:
if (msg.event_type == EVENT_SINGLE_CLICK
and msg.sensor_id == SENSOR_BACK):
self._catch()
return
if self.phase == PHASE_IDLE:
if (msg.event_type == EVENT_SINGLE_CLICK
and msg.sensor_id == SENSOR_BACK):
self.start_show()
return
if self.phase == PHASE_DONE:
if msg.event_type == EVENT_SINGLE_CLICK:
self.bot.say("还要听?行,返场!")
self._restart()
elif (msg.event_type == EVENT_DOUBLE_CLICK
and msg.sensor_id == SENSOR_CHEST):
self.bot.say("谢谢大家,我回去杵着了")
self.bot.play_led(0)
return
四个相位四种待遇:SPEAKING 相位什么都不接(乱拍忽略,防误触不打断台词)、APPLAUSE 相位只认背部单击、IDLE 等开演、DONE 等返场或散场。加锁是因为 TTS 回调和触摸回调可能同时进,相位读改要互斥。
“不接就吐槽”是这个技能的设计核心——冷场是演出事故,吐槽是兜底包袱。哪怕全程没人拍背,机器人也会自己把节奏走下去,谢幕时统计“这一段你捧了 0 次,专业程度百分之 0”,吐槽感拉满,反而成了效果最好的结局。
三、为什么应该把段子设计为数据
两段相声的文本全部放在 src/script_data.py 里,每条台词是一个字典:
{
"t": "主人给我定的 KPI,就三个字——别摔了",
"wait": True, # 这句之后进捧哏窗口
"emo": 18, # 播这句时的表情
"led": "white_breath",
}
如果需要加一段新相声,那么不需要动引擎。这个分离带来的直接好处是:段子可以随时改、随时加,而节奏逻辑只在一处维护。真实的数据长这样:
SCRIPTS = {
"motian": {
"title": "机器人也想摸鱼",
"lines": [
{"t": "大家好,我是这台桌子上的机器人", "wait": True,
"emo": 10, "led": 20},
{"t": "别的机器人满世界跑,我负责在这儿杵着", "wait": True},
{"t": "主人给我定的 KPI,就三个字——别摔了", "wait": True,
"emo": 260},
{"t": "我唯一的激动时刻,是充电", "wait": True},
{"t": "电量从百分之十九跳到二十,我恨不得给自己鼓掌",
"wait": True, "emo": 19, "led": 1},
{"t": "谢谢大家,我回去杵着了", "wait": False, "end": True},
],
},
}
原则写在 script_data.py 的模块注释里:机器人视角自嘲,包袱落在“机器人的物理局限与社会身份错位”上

crosstalk 的三层结构:数据层(段子文本)、节奏引擎(时间戳推进的相位机)、执行层(TTS/表情/灯带),三层之间只靠列表和接口通信。
两段原创段子随仓库开源:
-
《机器人也想摸鱼》——“主人给我定的 KPI 就三个字:别摔了”“电量跳动是我唯一的激动时刻”
-
《聪明反被聪明误》——“我懂八国语言的问候,就只会一句收到收到”“聪明是直接报数据”
四、在官方预设动作上二次调整
PrimeConsole 的动作面板里有 14 项官方预设(打招呼、握手、比心、站起、变形……),看起来“让机器人表演”直接调预设就行。我们评估之后的结论是预设动作解决不了这个场景:
| 维度 | 官方预设动作 | 自写台词引擎 |
|---|---|---|
| 内容 | 固定动画,文本写死 | 任意文本,改列表就行 |
| 节奏 | 一段完整播放,不可拆 | 逐句可控,句间任意间隔 |
| 互动 | 播放中不可插入 | 夸奖、返场随时插播 |
| 表情配合 | 动画自带 | 每句可配不同表情 ID |
相声需要的是台词级控制——每一句说什么、停顿多久、配什么表情,预设动画给不了。所以我们只用预设动作体系之外的东西:TTS + PlayEmotion + LedStripCommand,三个都是已在 touch_game 里验证过的通道,风险为零。
五、节奏控制:没有语速参数的引擎
相声的灵魂是节奏,而 SDK 的 TTS 接口没有语速参数——PlayTtsRequest 里只有文本、优先级和是否打断。下面是节奏设计:
-
每句独立 TTS 调用——句与句之间由代码控制间隔,而不是让 TTS 一口气播完
-
句间间隔按字数估时——
间隔 = 字数 ÷ 4.5 字每秒 + 0.9s 缓冲,4.5 是经验值,真机上跑几遍就能标定准 -
捧哏窗口固定 2.5s——估时只影响“下一句什么时候开始”,不影响窗口本身的判定
整个时间线由一个 5Hz 的心跳按时间戳推进,相位机 SPEAKING → APPLAUSE → SPEAKING → FINISHED。核心只有 _speak_next:
def _speak_next(self):
line = self.script[self._line_idx]
self._line_idx += 1
self.bot.say(line["t"])
self.bot.play_emotion(line.get("emo"))
self.bot.play_led(line.get("led"))
if line.get("end"):
self._finish()
return
tts_s = max(1.5, len(line["t"]) / 4.5)
if line.get("wait"):
self.phase = PHASE_APPLAUSE
self._deadline = time.monotonic() + tts_s + self.applause_window
else:
self.phase = PHASE_SPEAKING
self._next_line_at = time.monotonic() + tts_s + 0.4
max(1.5, ...) 是短句下限——四个字的短句按公式算出来不到 1 秒,捧哏还没反应过来窗口就关了,演出节奏直接垮掉。这里又一次用上了 touch_game 的教训:不用周期定时器做状态转换,只用心跳读时间戳。
is_interrupted=True 是——夸奖语、返场、散场随时可能插进来,不能让上一句排队等着播完。
六、--sim 验证:一次四犯的定时器错误
--sim 注入拍背事件走完整流程。第一次运行就撞上了那个熟悉的坑——_run_sim 注册了心跳和注入定时器,但忘了进 spin:
self.node.create_timer(1.0 / TICK_HZ, self._tick)
self.start_show()
self.node.create_timer(3.0, self._sim_applause)
self.node.create_timer(20.0, self._sim_done)
# 然后 run() 返回,main 里 shutdown——一个定时器都没执行
这是同一个错误在三个技能里的第四次发作(touch_reply 两次、touch_game 一次、crosstalk 一次)。修复一行:_run_sim 末尾补 rclpy.spin()。这次我们没再靠“记住教训”——把它写成了流程:所有 sim 入口的验收清单里加了一条“确认 timer 注册后有 spin 驱动”。

--dry-run输出:段子《聪明反被聪明误》12 句、捧哏窗口 2.5s、背部单击 = 接话/下一段、胸部双击 = 退出——部署前的行为契约。

--sim全程实录:12 句台词逐句播报,拍背接话 6 次全部 caught,夸奖、表情、灯带联动准确,谢幕统计后干净退出。
七、部署上板与发布
部署流程和前两个技能完全一致,这次一次通过——cfg/pkg.yaml 的教训已经记进检查清单:
python3 pack_rpk.py # crosstalk-1.0.0.rpk,8 files

打包输出:包大小与文件数。

包内容:manifest.json(4 项能力)、cfg/pkg.yaml(技能身份)、assets/icon.png、META-INF/checksums.json 加源码五件套。

PrimeConsole 解析结果:skillid = crosstalk,原子能力三项(表情播放 / 语音合成 / LED 灯效)。

部署成功:crosstalk-1.0.0.rpk 安装到机器人,界面显示“部署成功”。

部署后的 PrimeConsole 主界面:机器人三维模型与实时视频同屏,部署链路对机器人本体的状态可见。
发布表单也走同一套元数据:

PrimeHub 发布表单:技能名称“人机相声搭档”,分类视觉/语言/动作,标签卧室/亲子/客厅——分类与标签多选,覆盖家庭场景的检索词。
八、常见问题排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 拍背没反应 | 拍在台词播放中,不在窗口期 | 等台词结束、灯带给出提示再拍 |
| 夸奖语不播 | TTS 上一句还没播完,新请求排队 | 确认 is_interrupted=True |
| 下一句迟迟不来 | 台词估时偏长 | 调小 4.5 字/秒 这个经验值 |
| 谢幕统计次数不对 | 窗口期判定用了事件到达时刻而非相位起点 | 检查心跳 tick 的时间戳来源 |
--sim 打完招呼就退出 | 又是没进 spin | 验收清单第一条 |
九、已知限制
-
台词时长按 4.5 字/秒估算,真人 TTS 语速有快有慢,估偏了捧哏窗口起点会前移或后退,需要真机标定
-
拍背必须落在窗口期,播放中拍会被忽略——节奏感是玩法的一部分,但确实会让人觉得“我明明拍了”
-
全部实时 TTS,没有预录音频,TTS 服务的延迟直接体现在演出节奏上
结语
crosstalk 是三个技能里“技术最少、表演最多”的一个:没有视觉、没有运动,全部技术含量集中在节奏引擎那几十行。但它回答了另一个问题——机器人除了“听话干活”,还能不能“陪你玩”。段子写在数据里,改文本就能加新活儿;捧哏窗口只有 2.5 秒,但这两秒里人是真的在跟机器互动。
本文使用的 SDK 与消息包均来自启元机器人开发者社区,TTS 与触摸接口以官方 t1-sdk 仓库定义为准。
参考资料
-
启元机器人开发者社区(SDK/文档下载):启元机器人开发者社区 · 一站式具身智能研发平台
-
官方 t1-sdk 仓库(含 aimdk_msgs 消息包):t1-sdk:基于 ROS2 与 FastDDS 的机器人二次开发 SDK 项目 - AtomGit
-
本文配套仓库 crosstalk:crosstalk:基于 Python 和 TTS 的交互式相声机器人项目 - AtomGit
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)