04 · 信号链路与下游消费

值离开滑块之后去哪了:propId → 中间件(3-MW)→ pps/MCU,回程 pub → 缓存 → 订阅者。 代码行号以 rls-xcd-u-mp-26-v7.0(HEAD 70f7b9afd)为准,行号约定同 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_LEVEL1631614333 = 0x6140757dINT32,0100(主驾自定义页 1100);App 侧恒等映射(03 §4.1)
禁用原因Driving.ENERGY_REGENERATION_BAN_REASON1631614331 = 0x6140757b0=NO_INHIBIT 可用;1 战斗/2 湿滑(雪地 SNOW 同值)/3 ADAS/4 除锈 RSTREMV/5 陡坡缓降 HDC/6 防晕车/7 拖挂
档位(预设/自定义)Energy.REGENERATION_MODE,App 别名 DrivingCtrl.BRAKING_MODE(MiCarPropertyIds.java:993)1631617312 = 0x61408120LOW=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_STATUS1631616408 = 0x61407d98
Custom(M2)Driving.REGENERATION_PARAMETER_MODE2_STATUS1631616396 = 0x61407d8c
MainCustom(经济/舒适/运动/运动+)REGENERATION_PARAMETER_{ECONOMIC,COMFORT,SPORT,SPORT_PLUS}_MODE_STATUS1631616363 / 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 SET22 次逐值: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 set22 条 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 * Ignore7 条,全部丢弃(自身 SET 的反馈,§4):202507、:203034、:203233 …
BatteryHandler7 条,值与 pub 一致:202495(v=4 @22.998)、:203020(v=50)、:203237(v=77)

两个量化事实:

  1. 下行零损耗零变换:App SET → CARSIGNAL set → pps 帧,逐值相等 (SET→set 0.42ms、SET→pps 18ms,22/22 对账), 中间件/pps 段无责;不存在任何 100-x 反转或取整变换(05 篇 §3.4 同结论)。
  2. 发布节流: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 App10:57:33.281>>> Done set propVal: 70 to propId: 0x6140757d主 txt :2120900
2 中间件收33.283449CARSIGNAL int32_values : [70]:155594+2.4ms
3 pps 发出33.284172send to pps ok, n=7, d=[202,166,165,47,2,0,71]:155597+3.2ms
4 中间件回报33.409947CARSIGNAL pub p=0x6140757d,a=0,v=70:155658+129ms
5 App 回调33.429onChangeEvent id=0x6140757d, v=70 + * Ignore ... feedback: 70 + BatteryHandler mValue=70:212200 / :212201 / :212205+148ms

终值收敛 ≈148ms(SET→App 收到回报)。单次点击只有一次 SET(取整后一次), 没有拖动那种逐值流。这就是「点击取整」在日志里的指纹:单值 SET + 一次 pub + 一次 Ignore。

3.3 全窗口统计(数量级对账)

口径SETpub/回调
CARSIGNAL 覆盖窗(10:51:22~10:58:01)175 条 set / 175 帧 pps73 条 pub
App 主 txt 全程190 次 Done set propVal79 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/:106 updateCarModel() → :111-117 removeCallbacks(mUpdateRunnable)+post 防抖合并 → :120 mUpdateRunnable 重算雷达。
  • 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 行 tag com.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
  • 链路: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

链路(代码实证):

  1. getObservedPropertyIdSet(Braking :211-216)把 ENERGY_REGENERATION_BAN_REASON 加入观测集——BAN_REASON 变化会走 onHandlePropertyChange 回显路径。
  2. BaseVehicleProgressPrefController.updateProgress :179-201::183 setEnabled(isPropertyAvailable && isDependencyAvailable && isInnerLogicAvailable)。
  3. isDependencyAvailable(Braking :245-249)= EnergyRecoveryModeHelper.isEnergyRecoveryAvailable(BAN_REASON),实现 EnergyRecoveryModeHelper.kt:29-32:BAN_REASON == NO_INHIBIT(0) 且信号状态 Available 才 true。
  4. 置灰后 UI 行为:SliderPreference 加遮罩(:545-574),点击路由到 mClickListenerInDisable → showDisableToast(Braking :158-191 按 7 种原因弹各自文案; Custom/MainCustom 走 EnergyRecoveryModeHelper.showDisabledReasonToast :70)。
  5. 滑动中被置灰的保护: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: 0x6140757dVehicleControlManager.java:121
A’. App 回调主 txtCarSettings-CarPropertyCacheManager: <订阅者拼接> onChangeEvent id=0x6140757d, v=NCarPropertyCacheManager.java:109-129
A”. App 回显屏蔽主 txt* Ignore propId: 0x6140757d feedback: NCarPropertyMgrPreferenceController.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=N3-MW 进程
B”. pps 帧同上send to pps ok, n=7, d=[202,166,165,47,2,0,N+1]3-MW 进程
C. 下游主 txtBatteryHandler: 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 常见误读清单

  1. * Ignore propId feedback ≠ 没生效——是自身 SET 回声的主动屏蔽(§4)。
  2. pub 比 SET 少且滞后 ≠ 丢值——发布节流,看终值收敛(≤150ms,§3.2 实证 148ms)。
  3. pps d[3] ≠ 强度——175 帧恒 47;强度在 d[6] = 值+1(§3.4,+1 含义待 MCU 确认)。
  4. 雷达图没动 ≠ 值没下发——回收只占经济性轴 0.15 权重且映射到 20~100(§5.1)。
  5. 标签值 ≠ 系统值(修复前版本)——132401 的滞留双半面,见 05-排障实战-N801PROB-132401; 先查 ROM 所带 settings 是否含修复(Change-Id I4a34b69c3943fb0574e6fca89b181850db0e766e)。
  6. 拿 dev 分支行号对 rls 代码——漂移 1~6 行,以符号定位(03 篇头声明)。

关联篇章