Introduction
Android Automotive OS (AAOS) is rapidly becoming the platform of choice for in-car infotainment and intelligent cockpit systems. At its core, AAOS relies heavily on the Vehicle Hardware Abstraction Layer (VHAL) to interact with vehicle-specific hardware and sensors, including critical systems connected via the Controller Area Network (CAN) bus. While the standard VHAL provides a rich set of properties for common vehicle functions, there are often scenarios where custom CAN bus protocols or unique vehicle features require extending the VHAL to expose these capabilities to the Android framework and applications. This article provides an expert-level guide on how to approach extending the AAOS Vehicle HAL to integrate and manage custom CAN bus services.
Understanding the AAOS Vehicle HAL
The Vehicle HAL is a crucial component in AAOS, acting as the interface between the Android framework and the vehicle’s underlying hardware. It abstracts away the complexities of vehicle-specific communication protocols, such as CAN, LIN, Ethernet, and others, presenting a unified API to Android services and applications. The VHAL defines a set of standard properties (e.g., speed, gear, HVAC settings) accessible via the CarPropertyManager. The main interface for the VHAL is IVehicle, which implementations (like DefaultVehicleHal or EmulatedVehicleHal) must provide.
Key VHAL Concepts:
- Vehicle Properties: Defined by an ID, type (int, float, byte array, etc.), and access permissions (read, write, subscribe).
- Property Getters/Setters: The VHAL implementation exposes
getandsetmethods for property values. - Property Subscribers: Allows Android components to receive updates when a property’s value changes.
- HAL Implementation: Typically a native C++ service that communicates with vehicle ECUs or a CAN interface.
The Challenge: Integrating Custom CAN Protocols
Modern vehicles often utilize proprietary CAN messages for specific functions not covered by standard VHAL properties. Examples include advanced driver-assistance systems (ADAS) sensor data, specialized diagnostics, or unique infotainment controls. Directly accessing the CAN bus from an Android application is typically not allowed due to security and architectural constraints. Therefore, the VHAL serves as the ideal gateway for exposing these custom CAN-based functionalities.
Approach 1: Extending the VHAL with Custom Properties
The most common and recommended approach is to extend the existing VHAL by defining new custom vehicle properties. This keeps the custom functionality within the established VHAL framework, leveraging its security model and integration with the Android Car Service.
Step-by-Step Implementation Guide
1. Setting up the Development Environment
You’ll need an AOSP build environment set up for your target AAOS device. Familiarity with C++ and the Android build system (Blueprint/Android.bp, Makefiles) is essential.
2. Defining Custom Vehicle Properties
New properties must be defined within the VHAL type definitions. This typically involves modifying hardware/interfaces/automotive/vehicle/2.0/types.hal or creating an extension file if your HAL is a vendor-specific extension.
First, identify a suitable range for custom vendor properties. The VHAL specification reserves property IDs in ranges like 0x2100 to 0x2FFF for vendor-specific properties. Let’s define a custom property for a ‘Custom Engine Temperature Sensor’.
// hardware/interfaces/automotive/vehicle/2.0/types.hal or vendor-specific extension
enum VehicleProperty: int32 {
...
// Vendor-defined properties start here
CUSTOM_ENGINE_TEMPERATURE = 0x2100,
CUSTOM_GEAR_SHIFT_COUNT = 0x2101,
...
};
Next, define the property characteristics in a HAL interface definition, usually in the VehicleHal.cpp or a similar file where properties are registered.
// In DefaultVehicleHal.cpp or a similar VHAL implementation file
static const std::vector<VehiclePropInfo> sVehicleProperties = {
...
// Custom Properties
{CUSTOM_ENGINE_TEMPERATURE, VehiclePropertyType::FLOAT, 0, VehiclePropertyChangeMode::ON_CHANGE, {.access = VehiclePropertyAccess::READ, .usage = VehiclePropertyUsage::SENSOR}, 0, {}},
{CUSTOM_GEAR_SHIFT_COUNT, VehiclePropertyType::INT32, 0, VehiclePropertyChangeMode::ON_CHANGE, {.access = VehiclePropertyAccess::READ, .usage = VehiclePropertyUsage::SENSOR}, 0, {}},
...
};
CUSTOM_ENGINE_TEMPERATURE: A float type, read-only, changes on value change (ON_CHANGE), and represents a sensor.CUSTOM_GEAR_SHIFT_COUNT: An int32 type, read-only, changes on value change, and represents a sensor.
3. Implementing the VHAL Extension
Locate your VHAL implementation (e.g., DefaultVehicleHal.cpp, EmulatedVehicleHal.cpp, or your vendor’s specific HAL implementation). You need to modify the doSet and doGet methods (or their equivalents) to handle your new custom properties. These methods are responsible for reading from or writing to the actual vehicle hardware.
For custom CAN bus services, you’ll typically integrate a native C++ library that can interact with the CAN interface (e.g., using socketcan on Linux-based systems). This library will parse raw CAN frames and extract/inject the data corresponding to your custom properties.
// Excerpt from DefaultVehicleHal.cpp (simplified)
Return<void> DefaultVehicleHal::get(const hidl_vec<VehicleProp>& props, get_cb _hidl_cb) {
std::vector<VehiclePropValue> values;
for (const auto& p : props) {
VehiclePropValue v = {};
v.prop = p.prop;
switch (p.prop) {
case CUSTOM_ENGINE_TEMPERATURE:
// Read from CAN bus (example: specific CAN ID and data byte)
v.value.floatValues.resize(1);
v.value.floatValues[0] = getCustomCanEngineTemp(); // Placeholder for CAN read
v.status = VehiclePropertyStatus::AVAILABLE;
break;
case CUSTOM_GEAR_SHIFT_COUNT:
v.value.int32Values.resize(1);
v.value.int32Values[0] = getCustomCanGearShiftCount(); // Placeholder for CAN read
v.status = VehiclePropertyStatus::AVAILABLE;
break;
// ... other properties
default:
// Handle standard properties or return NOT_AVAILABLE
v.status = VehiclePropertyStatus::NOT_AVAILABLE;
break;
}
values.push_back(v);
}
_hidl_cb(Error::OK, values);
return Void();
}
Return<void> DefaultVehicleHal::set(const hidl_vec<VehiclePropValue>& propValues, set_cb _hidl_cb) {
std::vector<VehiclePropStatus> statuses;
for (const auto& p : propValues) {
switch (p.prop) {
// If CUSTOM_ENGINE_TEMPERATURE or CUSTOM_GEAR_SHIFT_COUNT were writable,
// you would implement the write logic here.
// For this example, they are read-only.
default:
statuses.push_back(VehiclePropertyStatus::NOT_AVAILABLE);
break;
}
}
_hidl_cb(Error::OK, statuses);
return Void();
}
// Placeholder function for reading CAN data
float getCustomCanEngineTemp() {
// In a real implementation:
// 1. Open socketcan interface (e.g., `socket(PF_CAN, SOCK_RAW, CAN_RAW)`).
// 2. Read CAN frames using `read()` or a dedicated CAN driver.
// 3. Filter for specific CAN ID and parse relevant bytes.
// 4. Convert raw bytes to float (e.g., 0-255 mapped to -40C to 215C).
// Example: Dummy value
return 85.5f;
}
int32_t getCustomCanGearShiftCount() {
// Similar CAN reading logic
return 15000; // Dummy value
}
4. Building and Deploying
After modifying the HAL, you need to rebuild your AOSP image. Navigate to your AOSP source directory and run:
source build/envsetup.sh
lunch aosp_car-userdebug # Or your specific target
m -j$(nproc)
Once built, flash the new image to your AAOS device. Ensure your device has the necessary kernel modules and device tree configurations for CAN bus access (e.g., `can`, `can_raw`, `mcp251x`).
5. Interacting from an Android App
From an Android application, you can use the CarPropertyManager to interact with your new custom properties. You’ll need to define the custom property IDs in your app, mirroring the HAL definition.
// Java code in an Android app
import android.car.Car;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyManager;
import android.util.Log;
public class CustomCanService {
private static final String TAG = "CustomCanService";
private CarPropertyManager mCarPropertyManager;
// Define custom property IDs (must match HAL definition)
public static final int CUSTOM_ENGINE_TEMPERATURE = 0x2100;
public static final int CUSTOM_GEAR_SHIFT_COUNT = 0x2101;
public CustomCanService(Car car) {
mCarPropertyManager = (CarPropertyManager) car.getCarManager(Car.PROPERTY_SERVICE);
if (mCarPropertyManager != null) {
Log.d(TAG, "CarPropertyManager initialized.");
// Register for property updates
mCarPropertyManager.registerCallback(
new CarPropertyManager.CarPropertyEventCallback() {
@Override
public void onChangeEvent(android.car.hardware.CarPropertyValue value) {
if (value.getPropertyId() == CUSTOM_ENGINE_TEMPERATURE) {
Log.d(TAG, "Custom Engine Temp: " + value.getValue() + " C");
} else if (value.getPropertyId() == CUSTOM_GEAR_SHIFT_COUNT) {
Log.d(TAG, "Custom Gear Shift Count: " + value.getValue());
}
}
@Override
public void onErrorEvent(int propertyId, int zone) {
Log.e(TAG, "Error for propertyId: " + propertyId);
}
},
CUSTOM_ENGINE_TEMPERATURE,
CarPropertyManager.SENSOR_RATE_ONCHANGE
);
mCarPropertyManager.registerCallback(
// ... similar callback for CUSTOM_GEAR_SHIFT_COUNT
// Omitting for brevity
new CarPropertyManager.CarPropertyEventCallback() {
@Override
public void onChangeEvent(android.car.hardware.CarPropertyValue value) {
if (value.getPropertyId() == CUSTOM_GEAR_SHIFT_COUNT) {
Log.d(TAG, "Custom Gear Shift Count: " + value.getValue());
}
}
@Override
public void onErrorEvent(int propertyId, int zone) {
Log.e(TAG, "Error for propertyId: " + propertyId);
}
},
CUSTOM_GEAR_SHIFT_COUNT,
CarPropertyManager.SENSOR_RATE_ONCHANGE
);
} else {
Log.e(TAG, "CarPropertyManager is null.");
}
}
public Float getCustomEngineTemperature() {
if (mCarPropertyManager != null) {
return (Float) mCarPropertyManager.getProperty(CUSTOM_ENGINE_TEMPERATURE, 0).getValue();
}
return null;
}
public Integer getCustomGearShiftCount() {
if (mCarPropertyManager != null) {
return (Integer) mCarPropertyManager.getProperty(CUSTOM_GEAR_SHIFT_COUNT, 0).getValue();
}
return null;
}
public void shutdown() {
if (mCarPropertyManager != null) {
// Unregister callbacks
}
}
}
Approach 2: Direct CAN Bus Interaction via a Separate Service (Advanced)
For highly complex, real-time CAN interactions that do not map well to the VHAL property model (e.g., custom flashing tools, low-level diagnostics, or direct command injection), creating a separate native service might be considered. This service would run outside the VHAL, communicate directly with the CAN bus (e.g., via socketcan), and expose its functionality to Android apps via a custom AIDL interface.
Considerations:
- Complexity: Significantly increases complexity due to managing a separate process, IPC, security, and lifecycle.
- Security: Requires careful privilege management and SELinux policies to allow direct CAN access.
- Integration: Needs to be integrated into the AAOS boot process (e.g., via init scripts) and potentially Android system services.
While more flexible for certain niche use cases, this approach bypasses many of the benefits of the VHAL and is generally discouraged unless strictly necessary.
Considerations and Best Practices
- Security: Carefully consider which properties are exposed and what level of access (read/write) is granted. Implement robust validation to prevent malicious inputs from impacting vehicle safety.
- Performance: Optimize CAN bus communication and data parsing to minimize latency, especially for real-time sensor data. Avoid polling; use event-driven or subscription models.
- Error Handling: Implement comprehensive error handling for CAN bus communication failures, invalid data, and property access issues.
- Maintainability: Document your custom properties and their integration points thoroughly. Adhere to VHAL design principles where possible.
- Hardware Abstraction: Ensure your CAN interaction layer is abstract enough to support different CAN controllers or interfaces if your platform might change.
- Vendor Extensibility: Use the designated vendor property ranges to avoid conflicts with future standard VHAL properties.
Conclusion
Extending the AAOS Vehicle HAL to incorporate custom CAN bus services is a powerful way to unlock the full potential of your vehicle’s hardware within the Android ecosystem. By carefully defining new vehicle properties and implementing the underlying CAN communication logic within your VHAL implementation, you can provide a secure, standardized, and performant interface for Android applications to interact with unique vehicle features. While challenging, mastering this extension capability is crucial for developing advanced, custom automotive experiences on AAOS.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →