安装 Agent
DSH 需要 Node.js;WorkBuddy 直接从官网下载安装包。先选一个你顺手的工具。
DSH 官方仓库 ↗WorkBuddy 官方网站 ↗
先安装并连接一个 Agent,再打开本工作区获取模板,接着用自然语言让 Agent 帮你改出第一个技能。
这里同时提供 DSH / WorkBuddy 官方入口、可直接复制的 Agent 提示词、Q1/T1 示例和从模板到真机验收的完整路径。
这是给 DSH / WorkBuddy 使用的最小源码包。里面有功能包模板、构建/测试/部署工具和两份 Agent 工作流; 不包含 HTML 教程、旧虚拟环境、Token 和已生成的 `.rpk` 成品。
你只需要先完成下面四步:安装一个 Agent、连接模型、把本工作区交给 Agent、用自然语言描述想让机器人做什么。DSH 和 WorkBuddy 都能完成这条路径;技术细节和现场命令放在后面的进阶区。
DSH 需要 Node.js;WorkBuddy 直接从官网下载安装包。先选一个你顺手的工具。
DSH 官方仓库 ↗在 Agent 的设置里选择模型并保存凭证。先用一个小问题测试“能回答、能读文件”。
查看连接提示词 →把本目录作为工作区打开,确认 Agent 能看到 Example、MustRead 和 Tools。
先让 Agent 阅读规则,再让它改台词或流程,最后由它运行构建和离线测试。
复制第一条任务 →Agent 负责帮你读文件、改技能和运行命令,但它不能隔着互联网直接找到机器人。电脑执行命令时,必须能通过局域网里的 IP 地址找到机器人。最简单的理解是:电脑和机器人要连到同一个现场 Wi-Fi 或同一个路由器,才能互相“看见”。
电脑连接现场 Wi-Fi;机器人也连接这个 Wi-Fi。不要让电脑连手机热点、机器人连另一个热点,也不要使用访客网络隔离。
从现场 App、机器人网络设置或导师提供的信息中找到类似 192.168.112.222 的 IPv4 地址。每次换网络后 IP 可能变化。
在终端执行 ping 机器人IP。能收到回复,说明基础网络通常已经连通;完全没有回复时,先处理网络,不要急着改代码。
192.168.112.35,机器人 IP 是 192.168.112.222,前面三段相同,通常表示它们在同一个局域网。若一个是 192.168.1.x、另一个是 192.168.112.x,通常不是同一网段。最终以现场网络配置和实际连通测试为准。
# 1) 把下面的 IP 换成现场机器人 IP
ping 192.168.112.222
# 2) 先连接、读取真机动作表并上传技能(不运行)
python Tools/scripts/robot_run.py --model q1 --ip 192.168.112.222 --skill energy_station
# 3) 确认周围 1 米安全、动作表正确后,再运行技能
python Tools/scripts/robot_run.py --model q1 --ip 192.168.112.222 --skill energy_station --run
连不上时按这个顺序排查: ① 两台设备是否连的是同一个 Wi-Fi; ② IP 是否是当前网络下的最新地址; ③ Windows 防火墙或现场网络是否禁止设备互访; ④ 机器人是否开机并完成联网; ⑤ 先让现场导师确认网络,再重复探测命令。不要把公网 IP、手机热点地址或电脑自己的 IP 误填成机器人 IP。
.rpk 功能包。
按官方说明完成安装并打开 DSH。第一次使用时只做三件事:启动 DSH、选择当前 workspace、确认它能看到项目资料。
从官方入口下载安装 WorkBuddy。第一次使用时依次登录、连接模型、打开当前 workspace,然后让它确认目录和资料是否可见。
把下面提示词复制给 DSH 或 WorkBuddy,开始制作第一个机器人功能包:
请帮我制作一个启元机器人功能包。 我的创意:【填写,例如:生日祝福、倒计时合影、每日天气播报】 目标机器人:【Q1 / T1 / 还没确定】 是否需要联网:【不需要 / 需要】 请按这个顺序工作: 1. 先阅读当前 workspace 的 README.md、MustRead/01_从零开发Q1_T1技能_SOP.md 和 MustRead/02_机器人运行时契约与避坑必读.md; 2. 用一句话告诉我准备使用哪个现成模板,以及最终会生成什么 .rpk 功能包; 3. 优先从 Example/energy_station 开始,只有确实需要更多能力时再换更复杂的模板; 4. 先完成电脑上的修改、构建和测试,再告诉我结果; 5. 不要把电脑测试通过说成真机已经通过; 6. 最后列出:我改了什么、生成了什么、下一步如何连接机器人并验证。
不要照抄固定答案。请参与者自己想一个机器人功能,再把需求交给 Agent。 Agent 会根据对应的仓库 Skill 询问细节、选择模板、生成 `.rpk` 并完成电脑测试。
例如:迎宾、生日祝福、抽签、拍照倒计时、天气播报或互动问答。
告诉 Agent 目标机型、台词、动作顺序、是否联网和完成标准。
确认生成了 `.rpk`,电脑测试通过,再决定是否连接真机。
.rpk 功能包;离线测试通过。到这一步之后,才进入“连接局域网、使用 IP、真机运行”的章节。
如果你只是第一次制作功能包,可以先完成上面的 Agent 任务和电脑测试,再回来阅读这里。
这一部分解释 .rpk 安装包的组成、Q1/T1 的运行规则、真机连接方式、现场验收和常见问题。
.rpk 技能包到底是个啥? —— 其实就是把 .zip 压缩包改了个后缀名!扒开看里面就躺着这 3 样核心宝贝:
万能遥控器 robot 是管家自动塞进全局变量的!你要是手欠写一行 import robot,Python 找不到模块当场给你表演 ImportError 原地去世!且只准导 Python 3.10 标准库(time、json、random、urllib.request)。
robot.play_tts(text) 是个不管售后的急性子,调完 0.001 秒就返回!不等说完就发下一句会直接把上一句掐脖子憋回去。更调皮的是长句换气时状态会在 1↔2 假空闲抖动,必须盯到连续 0.7 秒稳定空闲(2)才算真说完!
黄金节奏:先等嘴巴彻底闭上 ➔ 再调 robot.play_animation(id, False) ➔ 再 time.sleep(秒数) 等它扭完。要是传了 interrupt=True,正在播的声音会被一刀切掉!
Q1(3000系)和 T1(1~72系)动作 ID 完全不通用!而且所有日志必须写 print(..., flush=True),否则全憋在内存缓冲区里,调试时屏幕比脸还干净。
reason 24):在普通用户室内模式(motion_mode=0)下,Q1 的腿被安全结界封印了——你非让它做全身动作(whole_body),底层硬件会直接甩给你一个冷酷的 reason 24!上架技能只准用上半身动作(upper_body,3000 系列),动动手照样帅翻全场:
reason 19):T1 一会儿是两条腿站立的酷盖(biped),一会儿是四条腿趴着的修勾(quadruped)。要是它正四条腿趴着呢,你非逼它直立比心握手,它当场报 reason 19 罢工(四条腿怎么比心嘛,臣妾做不到啊!)!必须先调 robot.get_t1_robot_form() 问清形态,且四足下只认 1 / 7 / 26 / 43 四个动作!
greet / cheer / heart 不用背数字)LUCKY_POOL 改词即变算命摊)"""启元百变互动官(Q1 / T1 通用全能技能模板,纯 Python 3.10 标准库)。
设计目标:
1. 开箱即用:可直接打包部署到 Q1 或 T1 真机运行,完成「迎宾 → 每日金句(联网+断网兜底) → 随机抽签互动 → 谢幕」完整闭环。
2. 极易改编:只需修改顶部的配置区(台词、动作别名、抽签池、是否开启联网/灯光),即可快速改成迎宾、算命、播报、小剧场等任意技能。
3. 严守契约:不导入 robot / ROS2,内蔵 TTS 0.7 秒防抖轮询与超时保护,自动适配 Q1 upper_body 与 T1 双足/四足动作白名单。
"""
import json
import random
import time
import urllib.request
# ==============================================================================
# 【第一部分:积木配置区 —— 改这里就能变成你的新技能!】
# ==============================================================================
# 构建脚本 build.py 会在打包时自动将此值替换为 "q1" 或 "t1"
ROBOT_MODEL = "q1"
# 功能开关
ENABLE_NET_QUOTE = False # True=尝试联网拉取每日一言(失败自动用离线兜底);False=纯离线最稳模式
ENABLE_RANDOM_DRAW = True # True=从 LUCKY_POOL 随机抽签;False=固定使用第一支签
ENABLE_MOOD_EFFECTS = True # True=尝试联动呼吸灯带与面部表情(自带安全容错)
# 联网 API 配置与断网兜底金句
QUOTE_API_URL = "https://v1.hitokoto.cn/?c=i&encode=json"
FALLBACK_QUOTE = "代码写得稳,真机跑得准,每一次动手都是超酷的创造!"
# 随机互动签文池:(签文台词, 动作语义名)
LUCKY_POOL = (
("叮!为你抽到「真机丝滑上上签」!今天你的所有动作指令全部一次通过!", "cheer"),
("叮!为你抽到「脑洞大开幸运签」!你的创意全场最亮眼,评委看了都点赞!", "heart"),
("叮!为你抽到「零Bug欧皇签」!编译不报错,网络不超时,好运直接拉满!", "greet"),
)
# 主流程固定剧本:(动作语义名, 台词文本, 备注说明)
# 支持占位符:{quote} 会自动填入联网/兜底金句,{lucky_text} 会填入抽签结果
OPENING_LINE = "哈喽大家好!我是启元百变互动官,很高兴在活动现场见到你!"
CLOSING_LINE = "今日互动圆满完成!祝你玩得开心,作品大放异彩,我们下次见!"
# 语义化动作映射表:把好记的名字映射到 Q1 / T1 的安全动作 ID 和保持秒数 (动作ID, 等待秒数)
# - Q1 严格只用室内模式允许的 upper_body 动作(3000 系)
# - T1 四足/双足均默认使用四足白名单兼容动作(26 喜悦 / 43 律动),也可在 biped 下扩展
ACTION_TABLE = {
"q1": {
"greet": (3001, 6.5), # 招手
"cheer": (3008, 6.5), # 加油
"heart": (3007, 6.5), # 双手比心
},
"t1_quadruped": {
"greet": (26, 16.0), # 喜悦(四足安全白名单)
"cheer": (43, 16.0), # 律动(四足安全白名单)
"heart": (26, 16.0), # 喜悦(四足安全白名单)
},
"t1_biped": {
"greet": (26, 16.0), # 喜悦(双足同样安全可用)
"cheer": (43, 16.0), # 律动
"heart": (26, 16.0), # 喜悦
},
}
POLL_SECONDS = 0.1
IDLE_SECONDS = 0.7
# ==============================================================================
# 【第二部分:安全硬件底座 —— 防吞字轮询、形态校验、网络兜底(一般无需修改)】
# ==============================================================================
def audio_state() -> int:
"""读取并校验音频状态:1=播放中,2=空闲。"""
snapshot = robot.get_audio_play_state()
if isinstance(snapshot, dict):
state = snapshot.get("state")
if isinstance(state, dict) and state.get("value") in (1, 2):
return state["value"]
raise RuntimeError("AUDIO_STATE_INVALID")
def speak(text: str) -> None:
"""说话并等待真实播完(含 4 秒起声监控 + 0.7 秒防抖滤波 + 超时保护)。"""
print("SPEAK " + text, flush=True)
robot.play_tts(text)
start_deadline = time.monotonic() + 4.0
while audio_state() != 1:
if time.monotonic() >= start_deadline:
raise RuntimeError("TTS_NOT_STARTED")
time.sleep(POLL_SECONDS)
finish_deadline = time.monotonic() + max(len(text) * 0.45, 4.0) + 8.0
idle_since = None
while time.monotonic() < finish_deadline:
if audio_state() == 1:
idle_since = None
elif idle_since is None:
idle_since = time.monotonic()
elif time.monotonic() - idle_since >= IDLE_SECONDS:
return
time.sleep(POLL_SECONDS)
raise RuntimeError("TTS_NOT_FINISHED")
def trigger_mood(color: str = "#FF6B2B", mode: int = 3) -> None:
"""可选的灯带氛围触发(带安全容错,不影响主流程与离线替身测试)。"""
if not ENABLE_MOOD_EFFECTS:
return
play_light = getattr(robot, "play_light", None)
if callable(play_light):
try:
play_light(color, mode)
except Exception as exc:
print("LIGHT_SKIP " + str(exc), flush=True)
def fetch_quote() -> str:
"""获取今日金句:若开启联网则请求 API,超时或断网时 100% 自动回退到离线金句。"""
if not ENABLE_NET_QUOTE:
return FALLBACK_QUOTE
try:
req = urllib.request.Request(QUOTE_API_URL, headers={"User-Agent": "PrimeBot-Skill/1.0"})
with urllib.request.urlopen(req, timeout=2.5) as resp:
data = json.loads(resp.read().decode("utf-8"))
hitokoto = data.get("hitokoto")
if isinstance(hitokoto, str) and hitokoto.strip():
return hitokoto.strip()
except Exception as exc:
print("NET_FALLBACK " + str(exc), flush=True)
return FALLBACK_QUOTE
def resolve_action_profile() -> dict:
"""根据机型(Q1 / T1)与实时形态(biped / quadruped)选择安全动作表。"""
if ROBOT_MODEL == "q1":
return ACTION_TABLE["q1"]
if ROBOT_MODEL == "t1":
form = robot.get_t1_robot_form()
if form not in ("biped", "quadruped"):
raise RuntimeError("T1_FORM_UNKNOWN")
print("T1_FORM " + form, flush=True)
return ACTION_TABLE["t1_" + form]
raise RuntimeError("ROBOT_MODEL_UNKNOWN")
# ==============================================================================
# 【第三部分:主演出流程】
# ==============================================================================
def main() -> None:
profile = resolve_action_profile()
print("UNIVERSAL_SKILL_START model=" + ROBOT_MODEL, flush=True)
trigger_mood("#FF6B2B", 3)
# 1. 开场问候
speak(OPENING_LINE)
# 2. 组装三幕互动内容(迎宾问候 → 今日灵感金句 → 幸运抽签)
quote_text = fetch_quote()
lucky_line, lucky_action = (
random.choice(LUCKY_POOL) if ENABLE_RANDOM_DRAW else LUCKY_POOL[0]
)
steps = (
("greet", "第一幕,元气问候。先挥个手,给你打满今日份的开发者Buff!"),
("cheer", "第二幕,灵感广播。送你一句话:" + quote_text),
(lucky_action, "第三幕,好运盲盒。" + lucky_line),
)
# 3. 逐幕执行:先说话播完 → 再触发安全动作 → 等待动作完成
for index, (action_key, line) in enumerate(steps, 1):
speak(line)
animation_id, hold = profile[action_key]
accepted = robot.play_animation(animation_id, interrupt=False)
print("ANIMATION step=%d key=%s id=%d accepted=%s" % (index, action_key, animation_id, accepted), flush=True)
if not accepted:
raise RuntimeError("ANIMATION_REJECTED id=%d" % animation_id)
time.sleep(hold)
# 4. 收场谢幕与完成标记
speak(CLOSING_LINE)
print("UNIVERSAL_SKILL_DONE steps=%d" % len(steps), flush=True)
# 机器人运行时在注入全局 robot 对象后直接执行
main()
"""社交能量补给站:基于官方示例的 TTS 轮询与动作编排,纯标准库。"""
import time
ROBOT_MODEL = "q1"
POLL_SECONDS = 0.1
IDLE_SECONDS = 0.7
STEPS = (
"第一格,勇气充电。今天你已经迈出了第一步,给自己加个油!",
"第二格,灵感充电。先让一个小点子跑起来,再慢慢把它变精彩!",
"第三格,快乐充电。请收下这份机械式认真,和百分之百的祝福!",
)
# 等待时长为保守初值,现场按动作权威表、任务日志与实际观感调整。
MOTIONS = {
"q1": ((3001, 7.0), (3008, 7.0), (3007, 7.0)),
"t1": ((26, 18.0), (43, 18.0), (26, 18.0)),
}
def audio_state() -> int:
snapshot = robot.get_audio_play_state()
if isinstance(snapshot, dict):
state = snapshot.get("state")
if isinstance(state, dict) and state.get("value") in (1, 2):
return state["value"]
raise RuntimeError("AUDIO_STATE_INVALID")
def speak(text: str) -> None:
"""必须观测到起声及稳定空闲;超时终止,避免下一句截断当前音频。"""
print("SPEAK " + text, flush=True)
robot.play_tts(text)
start_deadline = time.monotonic() + 4.0
while audio_state() != 1:
if time.monotonic() >= start_deadline:
raise RuntimeError("TTS_NOT_STARTED")
time.sleep(POLL_SECONDS)
finish_deadline = time.monotonic() + max(len(text) * 0.45, 4.0) + 8.0
idle_since = None
while time.monotonic() < finish_deadline:
if audio_state() == 1:
idle_since = None
elif idle_since is None:
idle_since = time.monotonic()
elif time.monotonic() - idle_since >= IDLE_SECONDS:
return
time.sleep(POLL_SECONDS)
raise RuntimeError("TTS_NOT_FINISHED")
def choreography() -> tuple:
if ROBOT_MODEL == "t1":
form = robot.get_t1_robot_form()
if form not in ("biped", "quadruped"):
raise RuntimeError("T1_FORM_UNKNOWN")
# 两种形态均使用 26/43,属于手册所列的四足允许集合。
print("T1_FORM " + form, flush=True)
return MOTIONS[ROBOT_MODEL]
def main() -> None:
motions = choreography()
print("ENERGY_STATION_START model=" + ROBOT_MODEL, flush=True)
speak("欢迎来到社交能量补给站!不用扫码,送你三格好心情。")
for index, (text, (animation_id, hold)) in enumerate(zip(STEPS, motions), 1):
speak(text)
accepted = robot.play_animation(animation_id, interrupt=False)
print("ANIMATION step=%d id=%d accepted=%s" % (index, animation_id, accepted), flush=True)
if not accepted:
raise RuntimeError("ANIMATION_REJECTED id=%d" % animation_id)
time.sleep(hold)
speak("充电完成!愿你带着自己的点子出发。掌声送给每一个敢动手的人!")
print("ENERGY_STATION_DONE steps=%d" % len(STEPS), flush=True)
# 与官方示例一样,在运行时 exec 注入 robot 后直接执行。
main()
"""从 Example/ 下的技能源码构建 Q1/T1 双机型 .rpk 包及可审阅的发布元数据。"""
import importlib.util
import json
import shutil
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
WORKSPACE = ROOT.parent
DEV_KIT = ROOT / "primehub_dev_kit"
EXAMPLE_ROOT = WORKSPACE / "Example"
OUTPUT = ROOT / "dist"
SKILL_SPECS = (
{
"folder": "energy_station",
"pkg_prefix": "cans518_energy_station",
"publish_prefix": "publish",
"title": "社交能量补给站",
"short_desc": "三格好心情,给每个敢动手的人充个电",
"long_desc_tpl": (
"离线小剧场:开场、勇气/灵感/快乐三格充电、谢幕。"
"每格短台词配动作,无需联网或语音输入。"
"仅适配%s,使用预设动作,运行时请留出周围1米空间。"
"基于官方示例改编;台词和编排可在源码中修改。"
),
"icon": "skill-game.svg",
},
{
"folder": "universal_skill",
"pkg_prefix": "cans518_universal_skill",
"publish_prefix": "publish_universal",
"title": "启元百变互动官",
"short_desc": "迎宾问候+灵感广播+好运盲盒抽签的通用互动剧场",
"long_desc_tpl": (
"Q1/T1通用互动技能模板:包含元气迎宾、每日灵感金句(支持联网API与断网自动兜底)、"
"随机好运抽签及谢幕。仅适配%s,内置形态检测与TTS防抖轮询,运行时请留出周围1米空间。"
),
"icon": "skill-verify.svg",
},
)
def build_skill(pack_module, spec_cfg: dict) -> None:
source_dir = EXAMPLE_ROOT / spec_cfg["folder"]
for model, model_id, model_code in (("q1", 100, "robot_q"), ("t1", 101, "robot_t")):
with tempfile.TemporaryDirectory(prefix="qiyuan_build_") as staging:
skill_dir = Path(staging) / (spec_cfg["folder"] + "_" + model)
shutil.copytree(source_dir, skill_dir, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
script = skill_dir / "src/main.py"
source = script.read_text(encoding="utf-8")
assignment = 'ROBOT_MODEL = "q1"'
if source.count(assignment) != 1:
raise ValueError("期望恰好一个 ROBOT_MODEL 声明: " + str(script))
script.write_text(source.replace(assignment, 'ROBOT_MODEL = "%s"' % model), encoding="utf-8")
config = skill_dir / "cfg/pkg.yaml"
package_name = "%s_%s_20261024" % (spec_cfg["pkg_prefix"], model)
lines = []
for line in config.read_text(encoding="utf-8").splitlines():
if line.startswith("package_name:"):
line = "package_name: " + package_name
elif line.startswith("robot_model:"):
line = "robot_model: " + model
lines.append(line)
config.write_text("\n".join(lines) + "\n", encoding="utf-8")
pack_module.pack(skill_dir, OUTPUT)
metadata = {
"skillName": spec_cfg["title"] + " " + model.upper(),
"version": "0.1.0",
"skillDesc": spec_cfg["short_desc"],
"long_description": spec_cfg["long_desc_tpl"] % model.upper(),
"categoryIds": [104],
"categoryCodeList": ["action"],
"tagsIds": [127],
"tagsCodeList": ["companion"],
"robotModelIds": [model_id],
"robotModelCodeList": [model_code],
"iconUrl": spec_cfg["icon"],
}
publish_file = "%s_%s.json" % (spec_cfg["publish_prefix"], model)
(OUTPUT / publish_file).write_text(
json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
def build() -> None:
spec = importlib.util.spec_from_file_location("primehub_pack", DEV_KIT / "pack.py")
pack_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(pack_module)
OUTPUT.mkdir(exist_ok=True)
for spec_cfg in SKILL_SPECS:
build_skill(pack_module, spec_cfg)
if __name__ == "__main__":
build()
派体检官检查你的电脑上 Python 温室环境、国密加密库 gmssl、ssh/scp/curl 三件套以及 PrimeHub 市场匿名搜索通不通。
PREFLIGHT_OK 并搜出官方「51 点歌台」python Tools/scripts/preflight.py
再也不用专门下火狐了!随便用 Chrome / Edge / Brave / Firefox 登录 primehub.primebot.com 后,连浏览器都不用关,一键把最新登录凭证顺进保险箱。
my 子命令不报错,说明发版通行证 100% 绿灯# 1. 自动扫描 Chrome / Edge / Brave / Firefox 顺走最新 Token
python Tools/primehub_dev_kit/extract_token.py
# 2. 验明正身(查查当前账号下的技能列表)
python Tools/primehub_dev_kit/primehub_publish.py my
打开《启元百变互动官》源码最顶部的【积木配置区】,改改开场白、往 LUCKY_POOL 塞几条搞笑运势签,或者拨动联网金句开关(拔网线也能自动切回离线保底词!)。
robot 铁律ENABLE_NET_QUOTE = False # True=联网拉每日金句(没网自动用 FALLBACK_QUOTE 兜底!)
ENABLE_RANDOM_DRAW = True # True=从 LUCKY_POOL 随机抽签
LUCKY_POOL = (
("叮!为你抽到「真机丝滑上上签」!动作一次通过!", "cheer"),
("叮!为你抽到「脑洞大开幸运签」!全场为你点赞!", "heart"),
)
不用苦哈哈维护两份代码!构建器会自动把源码复印两份并分别注入 q1 / t1 参数,在 成品弹药库 里吐出 4 个 .rpk 安装包和 4 份上架小抄。
OK .../*.rpkpython Tools/scripts/build.py
还没连真机心里没底?用“虚拟快进时钟 + 假机器人替身”在电脑上 0.4 秒把 Q1、T1 双足、T1 四足、0.3 秒大喘气抖动、拔网线降级与花式报错熔断全测一遍!
OK(⚠️ 敲黑板:电脑替身彩排通过 ≠ 真机跑通!)python -m unittest discover -s Tools/tests -v
🚨 上机前保命三结界:① 周围 1 米内清走人类与水杯;② 先低位姿态试跑再站立测试;③ 运行期间谁也别拿手机 App 抢方向盘遥控!
SSH 提示密码时敲 1。第一遍先不加 --run 把机上真实 YAML 动作表抓回 验证与探测报告厅,核对无误后再加 --run 起跳!
final: success + 硬件日志印着 创建Animation task成功# 1. 第一步:先侦察真机环境、抓回机上 YAML 动作表并上传 .rpk 包(先不跑!)
python Tools/scripts/robot_run.py --model q1 --ip 192.168.112.222 --skill universal_skill
# 2. 第二步:确认周围 1 米安全净空后,追加 --run 正式点火起跳!
python Tools/scripts/robot_run.py --model q1 --ip 192.168.112.222 --skill universal_skill --run
Step 7(双响炮验收):原样再跑一遍 --run,确保连续两轮独立稳过且亲耳听完 5 段台词不吞字。
Step 8(上架封神):敲右侧命令上架 PrimeHub(注意同名重复发布会生出双胞胎条目,换版先 delete),最后掏出手机官方 App 搜到下载并当众跑一遍!
{'code': 1000, 'msg': '成功'} + 手机 App 下载演出成功!# 1. 一键国密加密上传并发布对应机型到 PrimeHub 技能市场
python Tools/primehub_dev_kit/primehub_publish.py publish Tools/dist/universal_skill_q1.rpk --metadata-file Tools/dist/publish_universal_q1.json
# 2. 查查自家在架列表与全网公开搜索验货
python Tools/primehub_dev_kit/primehub_publish.py my
python Tools/primehub_dev_kit/primehub_publish.py search "启元百变互动官 Q1"
| 日志报错 / 翻车现场 | 机器人在抱怨啥?(大白话翻译) | 10 秒钟怎么哄好它? |
|---|---|---|
| reason 24 | Q1 在普通用户室内模式下,下盘被安全结界封印了,拒做全身动作(whole_body) |
乖乖换成 Q1 upper_body 上半身动作 ID(如 3001 招手、3007 比心、3008 加油) |
| reason 19 | T1 正四条腿趴着当修勾呢,你非逼它做双足站立专属动作(四条腿怎么比心嘛!) | 换成四足通吃王炸动作(26 喜悦、43 律动、1、7)或用 get_t1_robot_form() 分支 |
| reason 42 | 上一个动作还在扭呢,动作队列堵车了,新动作挤不进去 | 把上一个动作后面的 time.sleep(hold) 等待秒数调大一点 |
| reason 28 | 上一个动作自带“霸体”不可打断(比如 T1 动作 1 打招呼要慢悠悠扭 15s) |
老老实实等够它的完整时长(如 16~18 秒),或者换用短平快的动作 26 / 43 |
| reason 17 | 机器人翻遍了固件字典,也没找到你瞎编的这个动作 ID | 打开 验证与探测报告厅 里抓回来的机上真实 YAML 动作表核对 |
TTS_NOT_FINISHED |
单句台词写成老太太的裹脚布太长了,超出了语音轮询等待上限 | 把单句台词精简到 30 字以内,或者调大 speak() 里的超时余量 |
final: timeout |
整场演出磨磨唧唧超过了 deploy_test.py 默认 120 秒监工窗口 |
适当缩减动作等待秒数,把整场演出控制在黄金 60~90 秒内 |