5、Vehicle HAL 开发:VHAL 架构、属性定义与实现、属性订阅与回调、VHAL 模拟器实现

Vehicle HAL,简称 VHAL,是 Android Automotive OS 里最核心的硬件抽象层。说白了,它就是连接上层车载服务和底层车辆硬件的桥梁。你想想看,车机要读取车速、控制空调、检测车门状态,这些信号最终都得通过 VHAL 来传递。

我个人习惯把 VHAL 比作一个「翻译官」。上层服务说 Android 的语言,底层硬件说 CAN 总线或 LIN 总线的语言,VHAL 负责在中间做双向翻译。嗯,这个比喻虽然简单,但很贴切。

5.1 VHAL 架构概览

VHAL 的架构其实不复杂,核心就三层:

  • VHAL 接口层:由 Android 框架定义,主要是 IVehicle.hal 这个接口文件。它规定了所有 VHAL 实现必须支持的方法,比如 get()set()subscribe() 等。
  • VHAL 实现层:由 OEM 或 Tier-1 供应商完成。这一层负责对接实际的硬件,比如通过 CAN 总线读取车速传感器。
  • VHAL 客户端层:也就是 CarService 和各个车载应用。它们通过 VehicleHalClient 与 VHAL 通信。

我在项目中遇到过一个问题:某家供应商直接把 CAN 报文解析逻辑写在了 VHAL 实现里,导致每次改一个传感器都得重新编译整个 HAL。这其实是不对的。VHAL 应该只做协议转换,具体的业务逻辑应该放在上层。

VHAL 三层架构 客户端层 CarService / 车载应用 VHAL 接口层 IVehicle.hal (get/set/subscribe) VHAL 实现层 OEM 实现 / CAN 总线对接 车辆硬件 (传感器 / ECU)

5.2 属性定义与实现

VHAL 里的每个数据点都叫一个「属性」(Property)。比如车速是 VEHICLE_SPEED,车门状态是 DOOR_POS。每个属性都有固定的 ID、类型、权限和变化模式。

属性定义通常在 types.hal 里完成。举个例子:

// 定义车速属性
enum VehicleProperty : int32_t {
    VEHICLE_SPEED = 0x1110,
    DOOR_POS = 0x1120,
    HVAC_TEMPERATURE = 0x1130,
};

// 属性配置
struct VehiclePropConfig {
    int32_t prop;           // 属性 ID
    int32_t access;         // 权限: 读/写/读写
    int32_t changeMode;     // 变化模式: 持续/脉冲/静态
    int32_t minSampleRate;  // 最小采样率
    int32_t maxSampleRate;  // 最大采样率
};

这里要注意 changeMode 这个字段。它决定了属性值变化时如何通知上层。我个人习惯这样区分:

变化模式 说明 典型场景
CONTINUOUS 持续变化,需要定期上报 车速、发动机转速
ON_CHANGE 值变化时才上报 车门开关、灯光状态
STATIC 只读一次,不会变化 VIN 码、固件版本
小技巧: 对于 CONTINUOUS 模式的属性,记得设置合理的采样率。我曾经见过一个项目把车速采样率设成了 100Hz,结果 CPU 占用率直接飙到 30%。其实 10Hz 对于大多数场景已经足够了。

5.3 属性订阅与回调

上层服务怎么知道某个属性值变了?靠的就是订阅机制。客户端调用 subscribe() 方法,VHAL 在值变化时通过回调通知客户端。

订阅流程大致是这样的:

  1. 客户端调用 IVehicle::subscribe(propId, sampleRate)
  2. VHAL 实现层注册对应的硬件监听器
  3. 硬件值变化时,VHAL 调用 onPropertyEvent() 回调
  4. 回调数据通过 VehiclePropValue 结构体传递

代码示例:

// 客户端订阅车速
Status subscribe(VehicleProperty::VEHICLE_SPEED, 
                 /* sampleRate */ 10.0f);

// VHAL 实现中的回调
Return<void> onPropertyEvent(
    const hidl_vec<VehiclePropValue>& values) {
    for (const auto& val : values) {
        if (val.prop == VEHICLE_SPEED) {
            float speed = val.value.floatValues[0];
            // 通知上层
            notifyClients(val);
        }
    }
    return Void();
}
注意: 回调函数是在 VHAL 的线程池中执行的。不要在回调里做耗时操作,否则会阻塞其他属性的上报。我曾经遇到过一个 bug,就是因为有人在回调里写了日志文件,导致车速更新延迟了 200ms。

5.4 VHAL 模拟器实现

开发阶段没有真车怎么办?VHAL 模拟器就是你的救星。它模拟了真实车辆的行为,让你可以在桌面上调试整个车载系统。

实现一个 VHAL 模拟器,核心就三步:

  • 实现 IVehicle 接口:所有方法都要有,但内部逻辑是模拟的。
  • 模拟属性值变化:用一个定时器线程,定期更新车速、转速等动态属性。
  • 提供控制接口:通过 ADB 命令或 UI 界面,手动改变模拟值。

下面是一个简化版的模拟器核心代码:

class VehicleSimulator : public IVehicle {
private:
    std::thread mSimThread;
    std::atomic<float> mSpeed{0.0f};
    
    void simulationLoop() {
        while (running) {
            // 模拟车速在 0-120 km/h 之间变化
            mSpeed = 60.0f + 30.0f * sin(time);
            
            VehiclePropValue value;
            value.prop = VEHICLE_SPEED;
            value.value.floatValues[0] = mSpeed;
            
            onPropertyEvent({value});
            std::this_thread::sleep_for(100ms);
        }
    }
    
public:
    Return<StatusCode> set(const VehiclePropValue& value) override {
        // 模拟设置操作
        if (value.prop == HVAC_TEMPERATURE) {
            // 模拟空调温度调节
            return StatusCode::OK;
        }
        return StatusCode::INVALID_ARG;
    }
};
关键点: 模拟器不仅要模拟正常值,还要模拟异常情况。比如车速突然跳变、传感器超时、值超出范围等。这样上层代码才能充分测试边界条件。

我记得有一次,客户反馈说他们的空调控制界面偶尔会卡死。我排查了半天,发现是因为模拟器里空调温度值一直返回 25.0,从来没变过。上层代码以为传感器坏了,一直在重试。后来我在模拟器里加了随机波动,问题就再也没出现过。

嗯,VHAL 开发就是这样。看似简单,但细节里全是坑。架构要清晰,属性定义要严谨,回调要高效,模拟器要逼真。把这几点做好了,你的 VHAL 就能稳定运行在千万辆车上。