从Q1迁到T1,同一个编号已经换了含义

对照Q1和T1的SDK时,有一处差异特别容易漏掉:两份 TouchState.msg 逐字节相同,sensor_id=1 的意思却变了。

Q1上是头部,T1上是背部。两种解释就写在同一份文件的注释里。

如果功能是"摸头后显示一个开心表情",代码从Q1搬到T1,可能照常编译、照常进入回调,甚至还能显示表情。只是触发它的部位已经换了。程序没有报错,原来的产品要求却没保住。

下面就沿着这个小功能,核对输入、表情调用和动作定义三处差异。使用的都是Q1/T1 SDK v1.0.0.0,文末给出可离线运行的核心判断。

1. 文件相同,为什么编号含义不同

两包 aimdk_msgs/hal/msg/TouchState.msg 的内容逐字节一致。它们都有消息头、传感器编号 sensor_id、事件类型 event_type,以及单击、双击和滑动等常量。

但相同文件里,恰好写着不同机型的解释:

text 复制代码
T:id为1表示背部传感器,id为2表示胸部传感器
Q:id为1表示头部传感器

字段类型没变,取值也合法,编译器当然查不出问题。它只认识这个字段能放一个编号,至于编号指向哪块传感器,要结合机型解释。

回到我们的功能。第一版如果写成"编号1单击时播放开心表情",迁移后仍可能命中同一个分支。但产品想保留的究竟是什么?

如果意图是"摸头才回应",T1的编号1就不能直接替代Q1的编号1。如果意图是"用户主动触摸时友好回应",则可以针对T1重新选择背部、胸部中的哪些触摸算有效互动。前一个需求强调部位,后一个强调交互意图,代码看起来相似,迁移决策却不同。

后面的例子选择第二种需求:主动触摸时友好回应。输入先按机型解释成身体部位,再配置哪些部位触发欢迎。这样,从Q1的摸头改成T1的背部触摸,就是一项看得见的产品选择,不会藏在同一个数字1后面。

2. 传入两个表情,最后只发出了一个

触摸输入解释清楚后,下一步是显示开心表情。两个SDK都有 examples/python/play_emotion.py,但默认值和回退时机不同。

Q1示例把 emotion_ids 默认设为 [10]。在表情模式下,发送前会检查列表:只要其中包含10,就把整个待发送列表改成 [3003]。T1示例默认设为 [90],先尝试原始请求;调用返回失败,并且原列表中含90时,再尝试 [3003]。

项目 Q1 示例 T1 示例
默认表情列表 [10] [90]
首次发送前 表情模式且含10时,整体改为 [3003] 尝试原始列表
首次失败后 该分支没有T1式的第二次尝试 表情模式且原列表含90时,再尝试 [3003]

例如,在Q1示例中传入 [10, 3004],最终发送的是 [3003],不是 [3003, 3004]。如果业务把这个列表当作一段连续表情编排,后一个元素也随整体替换消失了。移植时只检查"接口是否存在",很难发现这样的行为。

T1的先尝试、再回退,则意味着一次业务请求可能对应两次服务调用。若只在业务入口打一条日志,后面出了问题,很难看出失败发生在哪次调用。更合适的记录方式是保留原始意图、首次实际发送值、返回结果以及是否进入回退。

文件里还有一个值得注意的小矛盾:T1回退分支的注释写"ID 10失败",代码实际检查 90 in self.emotion_ids。阅读者如果只扫注释,会得到另一套规则;实际执行条件应以代码为准。

所以业务层最好只提出"显示开心表情",具体编号由目标环境的配置提供。示例里的默认值和回退值,可以拿来理解调用过程;直接把它们搬成全项目的资源规则,容易连同整体替换和失败重试一起搬过去。

资源依据:Q1文档列出3001---3013;T1示例中的90只是尝试值。示例源码不能确认目标设备实际安装了哪些资源。

3. 常量204,在一份包里有效,在另一份里被注释

如果欢迎功能以后还想加一个转身或模仿动作,就会接触 McAction.msg。这份文件把急停、形态切换、四足、双足和Q系列动作放在不同区段里。结构很像,常量却并不完全相同。

T1包有效定义了:

text 复制代码
int32 BIPED_LOCOMOTION_ROTATE_LOCAL = 204

Q1包的对应位置是:

text 复制代码
# 双足原地旋转(删除)
# int32 BIPED_LOCOMOTION_ROTATE_LOCAL = 204

反过来,Q1包末尾有有效的 MIMIC_V2=1018 与 MIMIC_OPENSOURCE_V2=1019,T1包中没有这两个定义。只使用一份SDK开发,再把"有这个常量"的假设带到另一份包,就可能在很早的构建阶段碰到问题。

但更隐蔽的风险来自两边都有的定义。两份文件都包含四足动作区段;Q1是人形机器人,文件里存在四足枚举,不能据此推断它具备对应的四足执行能力。协议可能表达多个产品共用的命令集合,具体设备只实现其中一部分。

要接这类动作,检查就得往下多走两步:目标包有定义之后,还要确认设备支持,再确认当前状态允许执行。否则找到一个常量名,距离真正做出那个动作仍然很远。

图 1:两份SDK的静态证据。上层共享命名和协议框架,下面分别列出字段语义、示例路径及枚举定义差异;图中的常量不作为真机能力清单。

4. 三处核对,得到了什么

读者可以从官方页面下载两包v1.0.0.0,按前文的文件路径逐项核对。我用离线脚本比较了触摸文件的哈希,通过Python语法树读取表情默认参数,再扫描三个动作常量是否为有效定义。得到的主要结果如下:

核对项 Q1 T1
TouchState.msg 文件内容 两者一致 两者一致
表情默认列表 [10] [90]
BIPED_LOCOMOTION_ROTATE_LOCAL 无有效定义 204
MIMIC_V2 1018 无有效定义
MIMIC_OPENSOURCE_V2 1019 无有效定义

以后升级SDK,这几项也值得再查一次。默认值变了,就重看首次发送和失败回退;动作常量变了,就找出依赖它的业务。比起拿所有功能从头试一遍,这张差异表能先缩小范围。

脚本检查文件哈希、默认参数和有效枚举;表情回退路径由源码阅读核对,设备资源与动作能力仍须运行验证。

5. 用同一个函数处理两台机器

前面的问题落到代码里,其实不用做一个很大的框架。把输入部位和输出资源各翻译一次,中间保留同一段欢迎逻辑,就够这个例子使用。

输入端结合明确的机型配置,把原始触摸编号解释成部位与事件。共享业务层只判断"这是不是当前欢迎流程允许的有效触摸",再产生"表达开心"的意图。输出端根据目标机型和版本查找已确认的资源,然后通过该设备对应的SDK调用服务。

图 2:本文提出的应用结构,不是官方SDK内置组件。机型配置负责解释输入和选择资源;公共业务逻辑处理互动意图。未知资源不在图中被自动视为可用。

欢迎流程只用"有效触摸"和"开心表情"这两个概念。编号、SDK版本及资源确认记录放进设备配置,服务调用结果再交回来。后面即使换了资源,也不必沿着所有业务分支找数字。

把这条路径缩成Python代码,核心判断只需要输入映射、业务条件和资源查询:

python 复制代码
def plan_reply(sensor_parts, accepted_parts, resources, sensor_id, event):
    part = sensor_parts.get(sensor_id)
    if part is None:
        return "unknown_sensor", None
    if event != "single_tap" or part not in accepted_parts:
        return "ignored", None
    resource = resources.get("happy")
    if resource is None:
        return "resource_unmapped", None
    return "ready", resource

Q1的部位映射填 {1: "head"},T1填 {1: "back", 2: "chest"}。两种配置交给同一个函数,输入先得到部位含义,再经过业务判断,最后查找资源。返回 ready 后,调用层才继续处理输出。

本例选择Q1头部单击、T1背部单击触发欢迎。这个选择属于我们的产品规则:T1胸部传感器仍可被识别,但暂不触发这段互动。三个主要用例的结果如下:

输入与配置 业务判断 输出
Q1编号1单击,已有演示资源映射 头部触摸有效 记录Q1开心资源
T1编号1单击,未配开心资源 背部触摸有效,资源缺失 resource_unmapped,不记录播放
T1编号1单击,补上演示资源映射 背部触摸有效 记录T1开心资源

演示使用的 demo:q1:happy 和 demo:t1:happy 是虚构测试资源名,不是设备ID;single_tap 是应用解码后的统一事件标签。程序不导入SDK、不连接设备。

中间一行是这段实现最有用的地方。背部触摸已识别,欢迎规则也允许,最后卡在资源缺失。此时应当补映射,输入和业务逻辑都不用动。"没有回应"终于有了一个具体原因。

把调用写出来,缺少映射和补齐映射的差别就更直观:

python 复制代码
t1_parts = {1: "back", 2: "chest"}
print(plan_reply(t1_parts, {"back"}, {}, 1, "single_tap"))
# ("resource_unmapped", None)

print(plan_reply(t1_parts, {"back"},
                 {"happy": "demo:t1:happy"}, 1, "single_tap"))
# ("ready", "demo:t1:happy")

离线演示还检查了胸部触摸未获业务允许、未知传感器和双击三种情况。它们分别停在自己的判断位置,因此"没有回应"可以沿路径查清原因。

真正接设备时,资源配置换成目标环境确认过的ID;收到 ready 后,由调用层通过对应SDK服务发起请求,并检查服务回执。这个替换把设备差异留在两端,欢迎逻辑仍然使用"有效触摸"和"表达开心"这样的业务概念。

6. 最后到设备上确认什么

离线用例已经能检查部位映射、欢迎规则和资源缺失分支。接上目标设备之后,再确认触摸实际来自哪个部位、所选表情是否存在、服务失败时返回什么。几种结果要和前面的分层对应起来,别最后又统统记成"播放失败"。

这次对比里,最值得提前处理的是 sensor_id=1 这样的语义差异。少了一个常量,程序可能直接报错;编号含义变了,程序却能一路跑下去,直到人觉得"怎么碰这里也有反应"才暴露出来。

把身体部位和资源映射写清楚,改动并不大。它让"摸一下,笑一下"先在两台机器人上各自成立,共享代码才有实际意义。

验证范围:本文完成SDK静态比较及离线分支演示,未连接真机。各设备应使用匹配的SDK构建环境;同名包不构成两份消息包混装或整体二进制兼容的证据。


依据:启元官方SDK下载中Q1/T1 v1.0.0.0的 TouchState.msg、play_emotion.py、McAction.msg 与接口说明;机型参考Q1官方产品页。欢迎在启元开发者社区交流跨机型开发经验。

相关推荐
Lynne3091 小时前
TSN 技术解析:如何应对工业网络中的延迟、抖动与丢包?
数据库·确定性通信·tsn时间敏感网络
2601_957084841 小时前
2026年工贸一体服装供应链SCM系统丨多品牌多工厂管控的架构设计:主数据分层、考核口径三要素与共享隔离模型
数据库·scm·服装供应链系统·服装scm·服装供应链
云和数据.ChenGuang1 小时前
光哥说Python 循环导入(Circular Import)深度解析:成因、排查与解决方案
开发语言·前端·人工智能·python·langchain
Aloudata1 小时前
语义资产运营指南:如何管理指标、维度、口径、Owner、血缘和使用记录
数据库·人工智能·数据分析·数据资产·dataagent·语义层
avi91111 小时前
【AI教人】在Inspector显示所有Delegate-Odin Inspector之三
java·前端·算法·delegate·inspector·editor扩展·unity editor
PaperData1 小时前
2006-2024年地级市能源消费强度数据
数据库·能源
fengci.1 小时前
BUUCTF(困难部分)
android·开发语言·前端·学习·php
Ward的AI产品实战1 小时前
开源 AI 网关 Magpie 调优终结篇:顺位排错、跨家族对抗审查与 5 大路由组重构(下篇)
前端·人工智能
Highcharts.js1 小时前
BI可视化图表“维护难”问题的解决方案
开发语言·前端·javascript·编辑器·ecmascript·bi·highcharts