本文档详细说明 M20 SDK 异常子系统的完整数据流、枚举设计、UI 展示逻辑以及国际化注意事项。
机器人(运控系统)
│ UDP/TCP 上报 Type=1002, Command=3
▼
RobotSdkManager (接收原始数据)
│ 回调 connection.readData()
▼
RobotStatesManager.parseResponse()
│ 匹配 CommandId.ROBOT_ERROR_STATE_MSG_ID
│ 提取 PatrolDevice.Items → JSON 字符串
▼
errorStatusInfoLive.postValue(json)
│ FilterMutableLiveData(自动去重)
▼
RobotErrorManager.parseErrorStates() ← IO 协程
│ 解析 {"ErrorList": [{errorCode, component}, ...]}
│ 逐个通过 整数 code 匹配 RobotErrorCode 枚举
▼
errorListLive: LiveData<MutableList<RobotErrorCode>>
│
├──→ MainActivity.updateErrorIndicator() ← 顶部状态栏指示器
└──→ ErrorListDialogFragment ← 异常列表弹窗
│ 调用 getErrorTitle() / getErrorContent()
│ 获取用户可见文本(@StringRes → 动态资源 → description 兜底)
▼
UI 显示
机器人运控系统以 2Hz 频率主动上报异常状态,消息格式:
{
"PatrolDevice": {
"Type": 1002,
"Command": 3,
"Time": "2026-06-15 10:30:00",
"Items": {
"ErrorList": [
{ "errorCode": 32769, "component": 1 },
{ "errorCode": 33026, "component": 0 }
]
}
}
}关键点:errorCode 和 component 均为整数,不包含任何中文文本。
| 字段 | 类型 | 说明 |
|---|---|---|
errorCode |
Int |
故障码,如 0x8001(32769)= 电机温度预警 |
component |
Int |
故障部件位掩码,bit0~bit15 对应 16 个关节(1=异常,0=正常)。非关节类异常此值为 0 |
表示机器人当前无异常,RobotErrorManager 会清空内部异常列表。
枚举匹配 完全基于整数 code 字段,与枚举标识符名称无关:
// RobotErrorCode.getError()
fun getError(errorCode: Int, component: Int): RobotErrorCode? {
return RobotErrorCode.values().firstOrNull { errorCode == it.code }
?.apply {
this.component = component
this.show = true
}
}这意味着枚举标识符改名(中文→英文)不影响故障匹配逻辑。 机器人上报 errorCode: 32769,SDK 遍历所有枚举值找到 code == 0x8001 的那一项,无论它叫 电机温度预警 还是 MotorTempWarning。
遍历 ErrorList 数组
│
├─ 本轮已出现相同 code → 合并 component(按位或)
├─ 上一轮已存在相同 code → 更新 component
└─ 全新 code → getError() 查找枚举,加入列表
│
▼
清空旧列表 → 替换为本轮结果 → postValue(errorListLive)
每 500ms(2Hz)收到一次上报,完整替换异常列表。上一轮存在但本轮不存在的异常码自动消失(表示故障已恢复)。
| 枚举类 | 职责 | 标识符语言 |
|---|---|---|
RobotErrorCode |
~113 个具体故障码,每个对应一种异常 | 英文 PascalCase |
RobotErrorLevel |
6 个告警等级(正常/恢复/次要/重要/紧急/关键) | 英文 PascalCase |
RobotErrorPart |
9 个运行域(关节/电池/主板/手动操控/外设/充电/自主/综合/采集) | 英文 PascalCase |
RobotErrorPartDetail |
~40 个异常对象(电机温度/电池电量/CPU占用率等) | 英文 PascalCase |
每个枚举条目均携带 val description: String 属性,存储原始中文文本:
enum class RobotErrorCode(
val part: RobotErrorPart,
val partDetail: RobotErrorPartDetail,
val level: RobotErrorLevel,
val code: Int,
// ... @StringRes 和其他字段 ...
val description: String = ""
) {
MotorTempWarning(
RobotErrorPart.JointMonitor,
RobotErrorPartDetail.MotorTemperature,
RobotErrorLevel.MinorWarning,
0x8001,
R.string.error_code_title_8001,
R.string.error_code_content_single_8001,
R.string.error_code_content_multiole_8001,
description = "电机温度预警"
),
// ...
}description 的用途:
getErrorTitle()/getErrorContent()在无字符串资源时作为兜底显示文本toFormatString()输出格式:"电机温度预警(MotorTempWarning)[0x8001,0b1]"- 日志输出:
desc=${error.description}
| 场景 | 命名规则 | 示例 |
|---|---|---|
| 通用故障 | 英文描述 |
MotorTempWarning |
| 同域相似故障 | 英文描述 + 数字后缀 | GaitSwitchFailed / GaitSwitchFailed2 |
| 缩写保留 | 保留行业通用缩写 | IMUDataNaN, RTKPositioningInaccurate, BMSDataTimeout |
| 5V 控制 | 数字前缀加下划线 | Control5V |
titleRes (@StringRes) 存在?
├─ 是 → context.resources.getString(titleRes!!)
└─ 否 → 动态查找 error_code_title_{HEX} 资源
├─ 找到 → context.resources.getString(resId)
└─ 未找到 → description(中文兜底文本)
│
▼
拼接格式:"$title (0xXXXX)"
多部件异常(component 含多个 bit=1)且 contentResMult 存在?
└─ 是 → getString(contentResMult!!)
单部件 且 contentResSingle 存在?
└─ 是 → getString(contentResSingle!!)
└─ 含 %s 占位符?
├─ 关节异常 → 替换为关节名称(R.array.joint_list)
└─ 电池异常 → 替换为电池名称(R.array.battery_list)
以上均不满足 → 动态查找 error_code_content_{HEX} 资源
├─ 找到 → getString(resId)
└─ 未找到 → description(中文兜底文本)
| 资源名 | 用途 |
|---|---|
error_code_title_{HEX} |
异常标题,如 error_code_title_8001 = "电机温度预警" |
error_code_content_single_{HEX} |
单部件异常描述,可含 %s 占位符 |
error_code_content_multiole_{HEX} |
多部件异常描述(注意:代码中拼写为 multiole) |
error_code_title_joint_base |
关节类通用标题 |
error_code_content_single_joint_base |
关节类单部件通用描述 |
error_code_content_multiple_joint_base |
关节类多部件通用描述 |
error_code_title_battery_base |
电池类通用标题 |
error_code_title_auto_charge_base |
充电类通用标题 |
未配置专用字符串资源的枚举条目,将 fallback 到 description 属性值。
| 等级 | 枚举名 | level 值 |
含义 |
|---|---|---|---|
| 正常 | Normal |
0 | 无异常 |
| 恢复 | Recovery |
1 | 故障消除 |
| 次要告警 | MinorWarning |
2 | 基础功能不受影响,部分功能受限 |
| 重要告警 | MajorWarning |
3 | 可自主运动,但无法自主作业 |
| 紧急告警 | CriticalWarning |
4 | 无法自主运动,可现场恢复 |
| 关键告警 | FatalWarning |
5 | 硬件故障,现场不可恢复 |
controlEnable() 判断链:
└─ errorList.hasError() → 存在 level ≥ 3 的异常时,禁止控制机器人
hasError() = isWarn() || isError() || isProtect()
其中:
isWarn() → level == MinorWarning (2) ← 仅预警,不阻断控制
isError() → level ∈ {MajorWarning(3), CriticalWarning(4), FatalWarning(5)}
isProtect() → level == FatalWarning (5)
| 函数 | 筛选条件 | 典型用途 |
|---|---|---|
isJointError() |
part == JointMonitor && partDetail != IMUData |
关节故障,触发关节名称替换 |
isBatteryError() |
part == BatteryMonitor |
电池故障,触发电池名称替换 |
isAutoChargeError() |
part == AutoCharging |
充电故障,影响充电流程判断 |
jointTemperatureErrorList() |
关节域 + 6 个温度相关枚举 | 温度异常专项筛选 |
component 是一个 32 位整数,bit0~bit15 对应 16 个关节:
bit 0 = 左前髋X关节(索引 0)
bit 1 = 左前髋Y关节(索引 1)
bit 2 = 左前膝关节(索引 2)
bit 3 = 左前轮关节(索引 3)
bit 4 = 右前髋X关节(索引 4)
...
bit 15 = 右后轮关节(索引 15)
bit 16~31 = 预留
多个关节同时异常时,component 为各位的按位或。例如左前髋X + 右前髋X 异常:component = 0b0000000000010001 = 0x0011。
SDK 内部通过 getJointErrorComponents() 提取所有置 1 的位索引,用于 UI 显示具体故障关节名称。
RobotErrorCode.show 字段控制同一异常是否重复弹窗:
- 首次出现的异常:
show = true,getNextShowError()会返回该异常 - 用户确认后调用
dissmiss():show = false - 异常消失后再次出现:
getError()重新设置show = true
// 依次获取待弹窗的异常
fun getNextShowError(): RobotErrorCode? =
errorList.filterErrorList().firstOrNull { it.isShow() }RobotErrorManager.hasDisConnectError() 不依赖枚举匹配,而是直接检查连接状态:
fun hasDisConnectError(): Boolean = !RobotStatesManager.isConnect()断连时 controlEnable() 直接返回 false,无需等待异常上报。重连后 RobotStatesManager.resetStates() 会清空异常列表,避免残留旧异常。
// MainViewModel.kt
val errorListLive = RobotErrorManager.errorListLive
// MainActivity.kt
viewModel.errorListLive.observe(this) { errorList ->
updateErrorIndicator(errorList) // 更新顶部状态指示器
}when {
errors.isNotEmpty() → 红色 "N项异常"
warns.isNotEmpty() → 橙色 "N项预警"
else → 绿色 "正常"
}SimpleDemo 提供长按触发测试异常的功能,构造模拟 JSON 注入 errorStatusInfoLive:
val json = """{"ErrorList":[{"errorCode":32769,"component":1}]}"""
RobotStatesManager.errorStatusInfoLive.postValue(json)| 设计决策 | 原因 |
|---|---|
| 枚举匹配用整数 code,不用 name | 机器人协议规定故障码为整数,name 仅是代码可读性工具 |
description 属性承载中文文本 |
枚举标识符国际化后,仍需保留中文作为 UI 兜底显示文本 |
@StringRes 优先于 description |
字符串资源支持多语言(中/英),description 仅作无资源时的 fallback |
FilterMutableLiveData 去重 |
相同异常 JSON 不重复触发解析,减少不必要的 UI 刷新 |
| 每轮完整替换异常列表 | 机器人每 500ms 上报完整异常快照,消失的故障码自动恢复 |