CarSettings—车控信号概述
一、车控信号承接现状
设置模块作为承接车控主要模块,目前主要接入的信号涵盖了车身域(大灯、雨刮、座椅、方向盘、盖后盖等)、底盘域(悬架高度、驾驶模式、EPB等)、运动域(加速特性、能量回收)、智驾域(主动安全、碰撞预警、车道偏离检测等)以及主驾屏(胎压、里程、能耗等)相关功能信号,另外空调、仪表均为独立模块承接对应的车控信号,除此之外,SystemUI、AVM等模块也有不同程度的车控信号承接。
1.1、主要模块
- 车身控制前后盖、充电口后视镜、方向盘、座椅调节
- 驾驶控制
- 驾驶偏好
- 自定义驾驶模式
- 赛道模式
- 能量管理
- 充放电
- 能耗曲线
- 车灯控制
- 车内灯
- 车外灯
- 辅助(智驾)驾驶
- 主动安全
- 驾驶辅助
- 智慧泊车
- 安全与服务
- 轻松载物、洗车模式等
- EPB
1.2、交互分布
- 车控一级页面, 如【控制中心】、【驾驶偏好】、【辅助驾驶】、【赛道模式】、【自定义驾驶模式】、【灯光】、【安全与服务】等;
- 车控内部弹窗,如后视镜、方向盘、座椅调节目前均使用dialog;
- 系统消息弹窗,如超级省电、尾翼调节、悬架高度调节;
1.3、车控控件类型
TabLayout 选项卡


2、Switch 开关类

3、ProgressBar 进度条类


4、Button 按钮类

5、Card 卡片类 (智驾页面)

6、其他


1.4、接入信号类型:
- 整型 95%+
- 浮点型
- 整形数组
目前整个车控暂无使用布尔型信号,均采用整型信号代替;
二、核心实现
2.1、页面结构
车控APP主页页面结构是HomeActivity + 多Fragment切换方式 ,Fragment通过配置PreferenceScreen xml文件完成页面要加载的Preference列表,SettingsFragment(车控页面基类)中解析preferencescreen ,然后使用RecyclerView加载Preference layout转化为itemview,完成页面UI内容的加载,Preference通过PreferenceController完成对Preference控件的控制,通常开发车控页面实现大概如下:
- 如果是新增车控页面,创建SettingsFragment, 并为其指定一个定义preference的xml文件,放到res/xml下;
- 针对车控项类型,采用合适的类型的Preference(TabLayout、Switch、SeekBar、CardView or 其他),按顺序填充到preference xml中;
- 为每一个preference配置对应类型的PropertyPreferenceController,几个高频使用的controller主要有:
- BaseSinglePropTabLayPrefController适配TabLayPreference, 主要用于处理单一信号多值多选一的场景,如驾驶模式切换、雨刮调节;
- BaseMultiPropCtrlPrefController适配TabLayoutPreference,主要用于组合多个开关类信号的场景,通常信号只有开关二态;
- BaseVehicleTabGroupLayPrefController适配TabGroupLayPreference, 用于展示同时具有多选一和其他单个信号的场景,如车外灯控制;
- BaseVehicleSwitchPrefController适配SwitchPreference, 用于开关样式车控项;
- BaseVehicleProgressPrefController使用SeekbarPreference, 用于区间等级调节类车控项, 如按键背光调节、雨量感应灵敏度调节;
- BaseAutoPilotCardPrefController该controller主要应用于智驾车控页面,对应的CardViewPreference兼具开关、选卡片弹窗以及页面跳转的交互需求。
📌 对于UI样式不具备通用特性的车控项需要实现新的 Preference,controller均需直接或者间接继承CarPropertyMgrPreferenceController,重写必要的信号处理方法实现,然后在PreferenceScreen xml文件中间关联对应controller。
2.2、核心业务逻辑
在处理车控信号中,每个(行)车控功能可以看成一个独立的小单元,由各自的Controller完成信号处理相关逻辑处理,Controller通过PropertyManager下发及接收信号,然后再将信号的状态刷新到preference封装的View上。
CarPropertyMgrPreferenceController作为所有车控信号处理controller的基类,封装了信号处理的核心流程,包括car service绑定及property manager的获取、注册监听信号及信号值分发、信号状态缓存、上报超时及信号下发失败处理、UI状态回退及关键日志打印等,针对以上关键节点汇总整理如下,可供大家快速了解核心流程作为参考:
2.2.1、Property Manager获取
Android应用层通过CarPropertyManager实现车控信号的设置和获取,在页面onCreate时,
Controller#onCreateInternal()中会通过Car对象完成carservice的绑定,然后拿到property manager对象。
2.2.2、信号监听注册
controller通过PropertyManger提供的register(id, propertyCallback)方法完成对信号的注册,当信号值有变化的时候,propertycallback提供的方法会被回调,同一个controller内监听的信号(单个或多个)使用一个CarPropertyEventCallback对象。
unregisterc操作在controller的destroy周期完成。
// in CarPropertyMgrPreferenceCcontroller
protected void registerPropertyCallback() {
// ...略
for (int propertyId: getObservedPropertyIdSet()) {
mVehicleCtrlManager.registerPropertyCallback(defaultCarPropertyEventCallback, propertyId);
}
MLog.v("......Register callback in %s for properties:【%s】",
getImplClassName(), idsBuilder);
}📌 在第一次完成register callback后,Framework CarService会主动上报一次信号的当前值。重复注册同一信号不会多次上报。 如果需要整个Controller要处理的多个信号都执行一次上报,可以先unregister再register来实现。 由于信号在register之后会上报一次当前值,所以针对每个信号值得第一次cache操作是在这个时机完成的。
2.2.3、信号下发(set property)
下发(设置)信号通过property manager提供的setXxxProperty(id, areaId, value)实现,其提供了
多种数据类型信号值的设置方法,如setIntProperty()、 setFloatProperty() 以及setProperty()支持如数组类型数据下发。Controller中对上述方法提供了对应的方法封装,以便在收到Preference的交互事件时下发信号。
信号下发需要等待预期值上报以便校准UI状态,但等待时间有上限(目前时2s),所以这时需要触发等待超时逻辑,延迟监听信号上报,在2s内成功上报移除延迟逻辑,否则触发等待超时对UI状态进行回退处理。
protected boolean setVehicleProperty(int propertyId, int propVal, int areaId) {
if (mVehicleCtrlManager != null) {
propertySupporter.addToTimeoutWatchList(mTimeoutPropertyIdMap.getOrDefault(propertyId, propertyId), areaId);
mVehicleCtrlManager.setIntProperty(propertyId, propVal, areaId);
return true;
}
return false;
}
protected boolean setVehicleProperty(int propertyId, float propVal, int areaId) {
if (mVehicleCtrlManager != null) {
propertySupporter.addToTimeoutWatchList(mTimeoutPropertyIdMap.getOrDefault(propertyId, propertyId), areaId);
mVehicleCtrlManager.setFloatProperty(propertyId, propVal, areaId);
return true;
}
return false;
}2.2.4、信号上报监听
信号变化通过注册的CarPropertyEventCallback接收,onChangeEvent(CarPropertyValue)
会被回调,这个时机需要考虑处理如 ①超时(移除or重新计时)处理、②信号值缓存、无效信号(unkwn、undef)处理、③ UI状态校准和置灰等逻辑。
如果设置信号失败,EventCallback接口有提供 ④onErrorEvent()回调,这个时候UI需要走状态回退的逻辑。同样在触发等待信号超时也需要回退UI状态。
private class CarPropertyEventCallback implements CarPropertyManager.CarPropertyEventCallback {
@Override
public void onChangeEvent(CarPropertyValue carPropertyValue) {
propertySupporter.logPropertyOnChange(carPropertyValue);
/**
* 1 移除信号上报超时监听
*/
propertySupporter.removeFromWatchList(carPropertyValue.getPropertyId(),
carPropertyValue.getAreaId());
/**
* 2 缓存上报的信号值
*/
cacheValueForRestore(carPropertyValue);
/**
* 3 处理信号,置灰UI,切换tab、打开关闭开关or整条进度条位置等
*/
onHandleCarProperty(carPropertyValue, shouldUpdateUI);
}
@Override
public void onErrorEvent(int propId, int areaId) {
onPerformRestoreEvent(propId, areaId);
}
public void onPerformRestoreEvent(int propId, int areaId) {
restorePropertyToPrevious(propId, areaId);
}
}📌 CAN信号的底层丢失(超过固定周期未上报),QNX CarService会触发兜底逻辑,上报信号为不可用,这时候UI收到上报,需要置灰UI。
2.2.5、绑定信号到UI
基类Controller中定义了真正响应信号的方法onHandlePropertyChange(),所有子类应实现该方法,并判断上报信号可用状态以及刷新UI状态(开关、tab切换、进度更新、文案变更等);
/**
* @param propertyValue
* @param shouldUpdateUI 信号上报至App时,UI状态是否需要更新, 特殊信号值(预留)会用到
*/
protected void onHandlePropertyChange(CarPropertyValue propertyValue, boolean shouldUpdateUI) {
}
以上为部分重写该方法的子类。
2.3 信号处理流程
📷 图示:信号处理流程图(readonly diagram block)
三、信号处理重点关注
3.1、信号(值)缓存
由于信号在设置后有可能出现设置失败或者超时上报的情况,这时候需要对UI状态进行回退处理,
故需要对上报的最新信号进行缓存。
由于存在不同车控功能使用信号id相同,但是域id不同,缓存时会把两个id结合起来作为key, 对信号值进行存储。
注意在缓存信号的时候需要注意值不能时中间态信号值和无意义的值,需要是一个终态值,以下3.3和3.4小结有解释说明。
📌 1、目前整个车控的主动交互大原则是UI状态先行(UI即时响应操作),信号上报后校验UI状态,故需要有cache保障UI回退时使用。 2、如只缓存了信号的值,可用状态未缓存可能在UI回退时无法准确展示是否应该置灰,可考虑直接将最新上报的CarPropertyValue对象进行缓存。该对象中包含了id, AreaId,value以及status等属性。TODO
3.2、超时处理(UI回退)
信号超时上报是需要进行UI回退的其中一个场景,在用户主动设置(切换)车控设置项时,会添加一个延迟消息到mesage队列,如到达消息处理时机还未收到信号上报,需触发UI执行回退。
📌 1、设置的信号与上报的信号不是同一个时,需要做好映射关系维护。 2、另目前车控模块的实现和SC要求有一定出入,SC要求2s后检测信号状态再去检验UI状态,目前是设置了2s超时时间,期间上报信号即刷新UI,超过时限回退UI,后其调整策略严格按照SC描述进行逻辑实现。TODO
3.3、无效信号处理
部分信号除了包含有实际含义的信号值外,还可能上报无实际意义的值,如 Unkwn / Undef,上报这些值时,SC通常要求APP层保持当前UI状态(开关、进度),但是需要UI进行置灰处理。
按照约定,大部分情况下Qnx CarService会针对这些信号值对上报的信号状态设置为UNAVAILABLE,但难免有遗漏,应用层可以针对这种case增加兜底处理。
另外这里需要区分是否为首次上报,即在打开页面完成信号注册后的首次信号上报,这时如收到一个无效值的时候,需要指定一个默认初始值并cache下来,以便在需要回退UI时使用。就这点不同域的SC中目前也无统一原则,大家写这些信号时注意甄别。
3.4、信号不可用
信号不可用状态通常用CarPropertyValue中的Status字段来表示,0和1分别表示可用以及不可用,
不过后来新增了直接上报信号值不可用的case,即CarPropertyValue的value为NOT_AVAILABLE。如遇到这类信号上报时UI是否需要置灰,需要做status和value == NOT_AVAILABLE的双重判断。
3.5、中间态信号
存在部分车控功能响应周期较长,交互需要体现信号响应过程,这类车控项有的上报中间态信号值,有的不会上报,需要注意的是中间态信号不作为长时间维持的状态值,UI不能长时间展示中间态形态,在指定等待时间后需要前进或者回退到一个终态值对应状态。
如果打开页面首次上报了中间态信号值,这时候也需要cache一个默认初始值,以便UI可以切换到一个终态;一般只有车控功能对应零部件响应周期较长的信号会定义中间态信号值,例如【后视镜折叠】、【EPB电子驻车】,另存在车控虽然不上报中间态信号,但需要有中间态的交互,如【充电口盖】。

3.6、信号设置失败
信号下发随后的信号上报反馈如果是设置失败了,这时候需要对UI进行状态回退处理。注意需要同时移除信号等待超时的延迟消息。
3.7、其他特殊交互场景
3.7.1、车控打开、关闭二次确认
存在部分车控项在关闭的时候交互上需要提供二次确认弹窗,再用户点击确认的时候才去真正下发信号,而在用户取消操作的时候,UI状态需要恢复到操作之前的状态。
实例:【副驾安全气囊关闭】以及部分的智驾车控关闭。

3.7.2、车控UI状态跳变
在进行进度条类车控控件操作时候,如在拖拽过程中如遇到信号上报,这时候需要忽略信号的响应,以免UI状态出现跳变的情况。类似的具有长时间操作的控件在处理信号时都需要考虑这种冲突。还有一种策略是在操作过程中不下发信号,直到抬手才去真正下发信号,以便规避这类问题。
3.7.3、监听物理按键
车控信号的下发除了操作HMI车控UI触发之外,还存在另外一种触发场景,HMI监听物理按键下发信号。比如方向键默认是用来控制音量和翻页操作的,但如果此时HMI打开了调解后视镜和方向盘的弹窗时,这时候方向键控制的就是对应UI页面功能了。
另外为了保证用户操作的交互体验,可能需要延迟下发信号,比如在通过滚轮调节后视镜时,key事件的上报会在极短的时间内完成上报,app如果全部即时响应下发信号,会出现硬件无法按照预期响应操作的现象。

3.7.4、UI状态回弹
UI回弹是指,在下发车控信号后,信号未能成功响应,随后上报的信号依然是操作之前的UI对应的信号值,由于UI状态先行,这时候需要将UI状态进行回退处理以保证UI状态符合当前车控信号值。
这点需要依赖信号的缓存机制,以便在需要回退的时候存在可回退到的状态(最后一次上报信号值)。
- 3.7.5、组合信号 车控功能有的监听单一信号即可,同时也存在需要监听多个信号的时候,这时候针对该类车控项需要同时注册一组信号,根据上报的信号处理车控HMI的是否置灰展示、是否可点击等逻辑。比如【超级省电模式】、【悬架高度调节】等。其中超级省电控制涉及到三个信号,悬架高度调解共涉及到了四个信号。

- 3.7.6、配置字 和配置字相关的车控项在E4阶段会逐渐增多,HMI需要根据配置字配置情况控制UI入口的显示隐藏以及其他逻辑处理。配置字的获取有两种,一种是通过车控信号API的方式获取,即通过CarPropertyManager注册监听对应信号获取,另一种是通过Settings Provider API获取,后者未覆盖支持所有的配置字,在使用前需要和FWK研发同学确认在使用。
3.7.7、其他
- 信号注册依赖PreferenceController生命周期,listview内容超过一屏高度时,没显示的内容(Preference)不会加载View,注册的信号上报后无法绑定到视图,这时候需要走延迟数据绑定逻辑以确保信号状态体现到视图上;(其他业务模块可结合自身情况参考)
- 信号过早上报,早于数据绑定到视图,可以考虑重新走注册流程。先unregister callback, 在重新register callback,这样CarService会上报所有注册了的信号值。