04 · 信号链路与下游消费
值离开滑块之后去哪了:propId → 中间件(3-MW)→ pps/MCU,回程 pub → 缓存 → 订阅者。 代码行号以
rls-xcd-u-mp-26-v7.0(HEAD70f7b9afd)为准,行号约定同 03-源码精讲。 日志实证全部取自 N801PROB-132401 附件bugreport-mach-UKQ1.240411.001-2026-07-23-10-57-54.txt(806968 行)及其内嵌FS/data/milog/milogcat/CARSIGNAL/milogcat(覆盖 10:51:22.169~10:58:01.771), 本地路径/home/zbc/下载/test/车机JIRA/jira-data/N801PROB-132401/attachments/bugreport/extracted/, 行号用grep -n可复现。
1. propId 三件套(javap 实证)
来源:com.mi.car.config:vehicle.support-0.11.58-SNAPSHOT.jar
(gradle 缓存 ~/.gradle/caches/modules-2/files-2.1/mi.car/vehicle.support/0.11.58-SNAPSHOT/...),
验证命令:javap -cp <解压目录> -constants mi.car.config.Driving / mi.car.config.Energy。
App 侧别名定义在 base/settingsBaseLib/.../vehicle/MiCarPropertyIds.java:993-997。
| 语义 | 常量(所属类) | propId | 取值 |
|---|---|---|---|
| 自定义强度 | Driving.ENERGY_REGENERATION_LEVEL | 1631614333 = 0x6140757d | INT32,0 |
| 禁用原因 | Driving.ENERGY_REGENERATION_BAN_REASON | 1631614331 = 0x6140757b | 0=NO_INHIBIT 可用;1 战斗/2 湿滑(雪地 SNOW 同值)/3 ADAS/4 除锈 RSTREMV/5 陡坡缓降 HDC/6 防晕车/7 拖挂 |
| 档位(预设/自定义) | Energy.REGENERATION_MODE,App 别名 DrivingCtrl.BRAKING_MODE(MiCarPropertyIds.java:993) | 1631617312 = 0x61408120 | LOW=1/SOFT=1、MEDIUM=2/STANDARD=2、HIGH=3/ADVANCED=3、AUTO=4/MODERATE=4(两套名字同值枚举,Energy$RegenerationMode javap 实证) |
自定义页两兄弟的 propId(随模式动态选,03 §3.2/3.3):
| 使用方 | 常量 | propId |
|---|---|---|
| Custom(M1) | Driving.REGENERATION_PARAMETER_MODE1_STATUS | 1631616408 = 0x61407d98 |
| Custom(M2) | Driving.REGENERATION_PARAMETER_MODE2_STATUS | 1631616396 = 0x61407d8c |
| MainCustom(经济/舒适/运动/运动+) | REGENERATION_PARAMETER_{ECONOMIC,COMFORT,SPORT,SPORT_PLUS}_MODE_STATUS | 1631616363 / 1631616393 / 1631616382 / 1631616383 |
本文链路实证(propId
0x6140757d)来自主页 controller 的一次真实操作; 自定义页的链路结构相同,仅 propId 换成上表对应值。
2. 全链路图
flowchart LR subgraph APP["com.android.car.settings (pid 1401000/5957)"] A["手指:拖/点/摸"] --> B["FusionSlider 事件流<br/>(micarx 组件库)"] B --> C["SliderPreference<br/>取整·过滤·计数<br/>03 §1"] C --> D["BaseVehicleProgressPrefController<br/>handlePreferenceChanged<br/>updateVehicleProperty (同步)"] D --> E["VehicleControlManager<br/>setVehicleProperty<br/>log: '>>> Done set propVal' :121"] end subgraph MW["3-MW vendor.micar.hardware.vehicle@1.0-service (pid 1506)"] F["prop client set vals<br/>(CARSIGNAL)"] --> G["send to pps ok<br/>d=[202,166,165,47,2,0,v+1]"] H["pub p=0x6140757d,v=N"] -. MCU 生效后回报 .-> I["CarPropertyCacheManager<br/>logChangeEvent"] end E == binder ==> F G == pps ==> MCU["MCU 执行"] MCU -.-> H I --> J["controller 自身<br/>(* Ignore 屏蔽自身反馈)"] I --> K["DrivingRadarRightFragment<br/>雷达图经济性轴"] I --> L["BatteryHandler<br/>com.micar.property.switcher (pid 10072)<br/>→ RecognitionMisManager → MIoT"]
3. 逐段实证(一段拖动 + 一次点击的真实对账)
3.1 拖动上行段 4→100:SET 密、pub 疏
10:57:22.838 起用户快速上拖,App 侧 SLIDER_NORMAL 跟手逐值下发:
| 段 | 观测 | 行号(主 txt / CARSIGNAL) |
|---|---|---|
| App SET | 22 次逐值:4,7,13,18,23,28,32,37,41,45,50,54,60,65,69,73,77,81,86,91,96,100(22.838~23.189,间隔 ~16ms) | 主 txt :202374(v=4)~:203211(v=100) |
| CARSIGNAL set | 22 条 int32_values:[N],与 App SET 逐值相等、无时差(<3ms) | :148746([4] @22.838433)~:149256([100] @23.191005) |
| pps 帧 | 22 帧 d=[202,166,165,47,2,0,N+1](见 §3.4 字节语义) | :148749(d[6]=5 @22.838985)~:149259(d[6]=101 @23.196624) |
| CARSIGNAL pub | 仅 7 条:v=4,28,37,50,77,86,100(22.975~23.318) | :148927(v=4)、:149017(v=28)、:149146(v=50)、:149514(v=100) |
| App onChangeEvent | 同样 7 条(pub→App 约 30ms) | :202506(v=4 @23.006)、:203033(v=50)、:203232(v=77) |
App * Ignore | 7 条,全部丢弃(自身 SET 的反馈,§4) | :202507、:203034、:203233 … |
| BatteryHandler | 7 条,值与 pub 一致 | :202495(v=4 @22.998)、:203020(v=50)、:203237(v=77) |
两个量化事实:
- 下行零损耗零变换:App SET → CARSIGNAL set → pps 帧,逐值相等
(SET→set 0.4
2ms、SET→pps 18ms,22/22 对账), 中间件/pps 段无责;不存在任何 100-x 反转或取整变换(05 篇 §3.4 同结论)。 - 发布节流:22 SET → 7 pub。pub 由车端按自身节奏回报,首条 pub(v=4 @22.975) 相对对应 SET(v=4 @22.838)滞后 ~137ms;拖动中 App 侧「显示值」始终比 pub 值新—— 这是设计好的(回显拖动保护,03 §1.7),不是丢值。
3.2 点击 66→70:五段时延逐跳(取整吸附的完整旅程)
拖到 66 松手(最后一个拖动 SET 66 @32.733),随后轻触轨道,
alignAdjustProgress half-up 吸附 66→70(03 §1.3):
| 跳 | 时刻 | 内容 | 行号 | 累计时延 |
|---|---|---|---|---|
| 1 App | 10:57:33.281 | >>> Done set propVal: 70 to propId: 0x6140757d | 主 txt :212090 | 0 |
| 2 中间件收 | 33.283449 | CARSIGNAL int32_values : [70] | :155594 | +2.4ms |
| 3 pps 发出 | 33.284172 | send to pps ok, n=7, d=[202,166,165,47,2,0,71] | :155597 | +3.2ms |
| 4 中间件回报 | 33.409947 | CARSIGNAL pub p=0x6140757d,a=0,v=70 | :155658 | +129ms |
| 5 App 回调 | 33.429 | onChangeEvent id=0x6140757d, v=70 + * Ignore ... feedback: 70 + BatteryHandler mValue=70 | :212200 / :212201 / :212205 | +148ms |
终值收敛 ≈148ms(SET→App 收到回报)。单次点击只有一次 SET(取整后一次), 没有拖动那种逐值流。这就是「点击取整」在日志里的指纹:单值 SET + 一次 pub + 一次 Ignore。
3.3 全窗口统计(数量级对账)
| 口径 | SET | pub/回调 |
|---|---|---|
| CARSIGNAL 覆盖窗(10:51:22~10:58:01) | 175 条 set / 175 帧 pps | 73 条 pub |
| App 主 txt 全程 | 190 次 Done set propVal | 79 onChangeEvent / 79 Ignore / 79 BatteryHandler |
- CARSIGNAL set 数 pps 帧数 App SET 数(窗口内 175)——下行 1:1:1,无合并无丢弃。
- pub 数(73)≈ SET 数的 42%;App 三个消费口(onChangeEvent/Ignore/BatteryHandler)恒相等(79), 说明 pub → App 分发也是 1:1:1,无丢失。
- 差出来的 15 次 SET(190-175)发生在 10:58:01 之后(CARSIGNAL 文件截止), 含 05 篇主案 10:58:19.538 的 SET 70(主 txt :794862)。
3.4 pps 字节语义(诚实声明 + 纠正)
send to pps ok, n=7, d=[202,166,165,47,2,0,X] 全窗口 175 帧实证:
- d[3] 恒为 47(175/175),在 App 下发 4/7/13/…/100 的全过程中不变—— 「d[3]=回收强度原值」的旧说法证伪(05 篇 §3.4 诚实修正已记录; 07 速查卡列为常见误读)。
- d[6] = App 下发值 + 1(175/175 全吻合):上行段 SET 4→d[6]=5、SET 100→d[6]=101 (:148749 起逐帧);下降段 SET 95→d[6]=96、SET 46→d[6]=47——d[6] 随值递减, 排除「序号/计数器」假设(计数器不会回头);点击 SET 70→d[6]=71(:155597)。
- d[0..2]=[202,166,165] 与 d[4..5]=[2,0] 全程不变,疑似消息头/子命令;pps 协议无文档背书, 「+1」的编码含义(疑 0=无效值占位)待与 MCU 侧确认,引用时保留此限定。
- 覆盖边界:CARSIGNAL 文件止于 10:58:01.771,05 篇主案(10:58:19 的 67→70)不在 pps 覆盖窗内;该事件的 MCU 段结论属合理外推(同窗口内机制已 22/22 验证),不是直拍。
4. 回显屏蔽:* Ignore propId 防回弹机制
主 txt 里每条 onChangeEvent 后紧跟一行:
07-23 10:57:33.429 I CarSettings: * Ignore propId: 0x6140757d feedback: 70 with areaId: 0 (:212201)
机制(代码实证):
CarPropertyMgrPreferenceController.onPropertyChanged :345-355:shouldIgnorePropertyFeedback(propId, areaId)命中 → 打 :350 的 Ignore 日志 → 走onIgnorePropertyFeedback(:352)后 return,不再刷新 UI。- 判据:
shouldIgnorePropertyFeedback :688-690→isInHMITriggerWaitingDuration :674-676——App 刚 HMI 触发(自己 SET)的等待窗内,该 propId 的反馈视为”自己引起的回声”,丢弃, 防止「值被回弹到中间态」。 - 但 controller 留了钩子:
BrakingStrengthPreferenceController.onIgnorePropertyFeedback :235-242在被忽略时仍把当前属性值推给 LiveData——UI 不动,雷达图照刷(§5.1)。 - 外部来源(MCU 主动改、超时恢复、其他 HMI)不在等待窗内,正常走
onHandlePropertyChange回显路径(03 §4.4)。
读日志纪律:Ignore 行 ≠「信号没生效」,恰恰相反——它证明 SET 已发出且回报已到达,
只是 App 主动不回显。全窗口 79 pub / 79 Ignore 全配对,说明这次操作期间该值没有外部扰动。
日志里的「页面身份证据」
CarSettings-CarPropertyCacheManager 的回调日志(:202506 等)前缀是:
BrakingStrengthPreferenceController@160834759|DrivingRadarRightFragment| onChangeEvent id=0x6140757d, a=0, s=0, v=70
这是同一 propId 的全部订阅者按 ”|” 拼接:
CarPropertyCacheManager.logChangeEvent :109-129(:121 取各 callback 的 holderName、
:125 拼 ”|”、:127 拼值);holderName 由
CarPropertyMgrPreferenceController.java:515 的
setHolderName(getImplClassName() + "@" + Objects.hashCode(this)) 产生
(controller@hash),Fragment 订阅者则登记为类名
(SettingsCarPropertyManager.kt:52/:80 mHolderName、:659-661 getCallbackName)。
排查时这行直接告诉你:哪些 controller/页面在盯这个信号——
本例即「主页 controller + 右侧雷达 Fragment」两个消费者。
5. 下游消费
5.1 雷达图经济性轴(DrivingRadarRightFragment)
DrivingRadarRightFragment.java:102:getDefaultViewModel().getLiveData(CarPropertyValue.class).observe(...)→ :95/:106updateCarModel()→ :111-117removeCallbacks(mUpdateRunnable)+post防抖合并 → :120mUpdateRunnable重算雷达。- LiveData 的两条喂数路径(03 §3.1):正常反馈
onHandlePropertyChange :224-232、 被 Ignore 的自身反馈onIgnorePropertyFeedback :235-242——两条都推,保证雷达始终跟最新值。 - 经济性轴公式(
base/settingsVehicleLib/.../common/AllWheelRadarUtils.java):getEconomyValue :214-221= (加速×0.3 + 尾翼×0.2 + 回收×0.15 + 悬架×0.15 + 扭矩分配×0.2)×0.4×0.1+5。 回收分项getBrakingStrengthValue :485-500:BATTLE 模式或brakingStrength非预设档 (即自定义 ADVANCED)时走getBrakingStrengthLevelValue(brakingStrengthLevel); 预设 SOFT/STANDARD/MODERATE 返回固定常量。 - 自定义等级映射
getBrakingStrengthLevelValue :510-520:
if (brakingStrengthLevel == 0) { return brakingStrengthLevel; } // :511-513 0 特殊直通
double result = ((double) brakingStrengthLevel - 1) / (100 - 1) * (100 - 20) + 20; // :515 (v-1)/99*80+20
return MathUtils.clamp(result, 0, 100); // :520即自定义滑块 1100 线性映射到雷达轴 20100(v=1→20、v=100→100),0 值直通 0。
「我把回收从 50 拖到 80,经济性雷达涨多少」:Δlevel=(79-49)/99×80≈24.2,
×0.15 权重 ×0.4 ×0.1 ≈ 0.15 个轴单位(轴总长 100)——
雷达对单次微调本就不敏感,别把「雷达没动」当「值没下发」。
5.2 BatteryHandler → RecognitionMisManager → MIoT(手机 App 可见)
- 主 txt:
BatteryHandler: onPropertyChange : CarPropertyValue{mPropertyId=0x6140757d, ..., mValue=70}(:212205,全窗口 79 条,与 pub 1:1)。 进程归属实证:pid 10072 =com.micar.property.switcher(:65907 行 tagcom.micar.property.switcher.recognition-RecognitionMisManager同 pid)。 - MIoT 上报实证(同进程,示例为另一属性 0x61407204):
- :65906
ICT-CHD: MIS-PG-DeviceBinderManager:fromTo:com.micar.property.switcher, reportSpec:{"id":49470000,"method":"properties_changed_v3","params":[{"did":"HXMQM8GFUAVWUPAB1","iid":"4.4.3","value":232}]} - :65907-65909
RecognitionMisManager: Received reportSpec / propChanged
- :65906
- 链路:
BatteryHandler收属性 →RecognitionMisManager组装 MIoT spec →properties_changed_v3上报云端 → 手机 App 车辆状态页可见。 ⚠️ 能量回收强度(0x6140757d)对应的 MIoT iid 在本次 bugreport 中未抓到上报实例 (窗口内 properties_changed_v3 样本是别的属性),「滑块值 → 手机 App」这一跳标 待验证:验证方法=台架拖一次滑块,logcat | grep properties_changed_v3抓RecognitionMisManager输出,确认 iid 与 value 跟随。
5.3 MCU 方向
下行唯一出口 = §3.4 的 pps 帧(3-MW pid 1506 =
vendor.micar.hardware.vehicle@1.0-service,主 txt :41560 的进程列表可证)。
MCU 执行侧(电机扭矩回收)不在 Android 日志面内,只能从回程 pub 值推断生效。
6. 门控链路(置灰 / 不可见)
6.1 可见性:档位 ≠ 自定义 → 滑块消失
BRAKING_MODE(0x61408120)≠ ADVANCED(3) 时滑块不可见。三重保险(03 §3):
controller onCreate 读缓存定初值 + ViewModel observer 持续跟随 +
onPropertyManagerPrepared 就绪再校一次。三个 controller 判据完全一致,
GT(mach)只有主页一处(01 篇车型门控)。
6.2 可用性:BAN_REASON ≠ 0 → 置灰 + 点出原因 toast
链路(代码实证):
getObservedPropertyIdSet(Braking :211-216)把ENERGY_REGENERATION_BAN_REASON加入观测集——BAN_REASON 变化会走onHandlePropertyChange回显路径。BaseVehicleProgressPrefController.updateProgress :179-201::183setEnabled(isPropertyAvailable && isDependencyAvailable && isInnerLogicAvailable)。isDependencyAvailable(Braking :245-249)=EnergyRecoveryModeHelper.isEnergyRecoveryAvailable(BAN_REASON),实现EnergyRecoveryModeHelper.kt:29-32:BAN_REASON == NO_INHIBIT(0)且信号状态 Available 才 true。- 置灰后 UI 行为:
SliderPreference加遮罩(:545-574),点击路由到mClickListenerInDisable→showDisableToast(Braking :158-191 按 7 种原因弹各自文案; Custom/MainCustom 走EnergyRecoveryModeHelper.showDisabledReasonToast :70)。 - 滑动中被置灰的保护:
onValueChange的 disabled return(03 §1.2②)与onSliderStopTrackingTouch的 disabled return(03 §1.5)保证「置灰瞬间手还在滑」 不会把非法值写下去。
7. 语音 / 云端路径
- 语音「能量回收调到 70」:
MiCarViewListener.performAction(OPERATION_SET_PROGRESS)(SliderPreference :247-265)——合法性检查后setValueInternal + callChangeListener直接下发,不经过触摸链的取整分支(取整只在 onValueChange/onStopTracking 手势链里)。 - 「调大一点/调小一点」:步长 =
STEP_FOR_VOICE_ASSIST = 10(三个 controller 同: Braking :57、Custom :49、MainCustom :43),即语音一步 = 点击取整一格。 - 云端约定:
SliderPreference.java:83注释「默认的Slider步长,如果设置为10, 云端会进行四舍五入」——步长 10 时云端对目标值四舍五入到 10 的倍数, 与车端 half-up 吸附语义对齐(车端 ≥5 进位,03 §1.3),两端落地值一致。
8. 排查指引:三视角对账法
8.1 三个视角的日志位置与指纹
| 视角 | 文件 | 日志指纹 | 代码出处 |
|---|---|---|---|
| A. App 下发 | bugreport 主 txt | >>> Done set propVal: N to propId: 0x6140757d | VehicleControlManager.java:121 |
| A’. App 回调 | 主 txt | CarSettings-CarPropertyCacheManager: <订阅者拼接> onChangeEvent id=0x6140757d, v=N | CarPropertyCacheManager.java:109-129 |
| A”. App 回显屏蔽 | 主 txt | * Ignore propId: 0x6140757d feedback: N | CarPropertyMgrPreferenceController.java:350 |
| B. 中间件下行 | FS/data/milog/milogcat/CARSIGNAL/milogcat(及 .gz 轮转,按时间戳挑文件 zcat) | prop client set vals : prop : 6140757d ... int32_values : [N] | 3-MW 进程 |
| B’. 中间件回报 | 同上 | pub p=0x6140757d,a=0,v=N | 3-MW 进程 |
| B”. pps 帧 | 同上 | send to pps ok, n=7, d=[202,166,165,47,2,0,N+1] | 3-MW 进程 |
| C. 下游 | 主 txt | BatteryHandler: onPropertyChange ... mValue=N(pid 10072) | com.micar.property.switcher |
⚠️ 主 txt 里的 CARSIGNAL section 是空壳,必须翻 FS/data/milog/milogcat/CARSIGNAL/
下的目录文件;轮转 .gz 按文件名时间戳挑覆盖事发时段的 zcat(07 速查卡同法)。
8.2 「值不一致」定界流程
flowchart TD S["用户看到值 X( slider 标签 / 手机 App / 车端行为 )"] --> Q1{"主 txt 有 SET X 吗?"} Q1 -- "无,或 SET 的是别的值" --> A1["App 侧:手势被取整改写/标签滞留<br/>→ 03 §1 + 05 篇"] Q1 -- "有 SET X" --> Q2{"CARSIGNAL set vals [X] 且 pps d[6]=X+1?"} Q2 -- "缺/不等" --> A2["中间件下行段丢/改<br/>(本单实证:从未发生,175/175 相等)"] Q2 -- "相等" --> Q3{"pub v=X 吗?"} Q3 -- "pub 滞后或值旧" --> A3["正常节流:等 ≤150ms 看终值;<br/>长期不收敛→MCU 未受理"] Q3 -- "pub=X" --> Q4{"BatteryHandler mValue=X 吗?"} Q4 -- "不等" --> A4["property.switcher 分发段"] Q4 -- "相等" --> Q5{"手机 App 值=X?"} Q5 -- "不等" --> A5["RecognitionMisManager→MIoT 上报段<br/>(iid 映射,§5.2 待验证项)"]
8.3 常见误读清单
* Ignore propId feedback≠ 没生效——是自身 SET 回声的主动屏蔽(§4)。- pub 比 SET 少且滞后 ≠ 丢值——发布节流,看终值收敛(≤150ms,§3.2 实证 148ms)。
- pps
d[3]≠ 强度——175 帧恒 47;强度在 d[6] = 值+1(§3.4,+1 含义待 MCU 确认)。 - 雷达图没动 ≠ 值没下发——回收只占经济性轴 0.15 权重且映射到 20~100(§5.1)。
- 标签值 ≠ 系统值(修复前版本)——132401 的滞留双半面,见
05-排障实战-N801PROB-132401;
先查 ROM 所带 settings 是否含修复(Change-Id
I4a34b69c3943fb0574e6fca89b181850db0e766e)。 - 拿 dev 分支行号对 rls 代码——漂移 1~6 行,以符号定位(03 篇头声明)。
关联篇章
- 03-源码精讲——本链路上每个 App 侧环节的代码逐段
- 05-排障实战-N801PROB-132401——同一 bugreport 的案件复盘(视频帧取证 + 双半面根因)
- 06-台架mock配方与测试矩阵——三信号 mock 自测
- 07-速查卡——propId/日志指纹/adb 命令一页纸