Android Automotive #3 — Vehicle HAL
The Vehicle Hardware Abstraction Layer (VHAL) is a service running alongside the rest of Android in order to control and query a car's…
Android Automotive #3 — Vehicle HAL
The *Vehicle Hardware Abstraction Layer (VHAL) *is a service running alongside the rest of Android in order to control and query a car's features. Essentially the VHAL is a property system, where you can get and set well-defined properties in order to interact with the car's electronic control units (ECU), it can be accessed by regular android applications using the CarPropertyManager. Still, for this post, we will look at it as a manufacturer that wishes to deploy Android Automotive in its car, and how it can do that. We will cover the following:
- The start point of the VHAL service.
- The details of the proposed emulation implementation included in AOSP.
- Adding a vendor property to the VHAL taking advantage of the proposed VHAL.
- Changing the included VHAL in order to make communication possible with an ECU.
VHAL entry point
The VHAL proposed implementation is built using the c++ language and it can be found in:
# on the sources root
cd hardware/interfaces/automotive/vehicle/2.0 # <- <vhalroot>
In that root folder, you can find some .hal files which represent the VHAL stubs and types that we will be using on our regular android applications. We will talk more about them in our next post when we tackle the VHAL access in a java application.
# on the sources root
cd hardware/interfaces/automotive/vehicle/2.0/default
The actual native VHAL implementation starts on the default folder, here you can find soong build system Android.bp, which references the libs that should be generated for the VHAL as well as the files which should be included.
The android.hardware.automotive.vehicle@2.0-service.rc file contains the necessary steps for the VHAL to be launched when the system boots up.
Finally, VehicleService.cpp contains the entry point for the VHAL. If you check that source file you will find the instantiation and setup for:
- VehiclePropertyStore
- VehicleHalManager
- EmulatedVehicleConnector
- EmulatedVehicleHal
- VehicleEmulator
Vehicle HAL
Android Automotive VHAL was created as a property system where you read properties in order to know the state of the car and write properties in order to alter its state. It also contains the ability to register a callback in order to be notified when a property changes. For example, if you want to know the battery capacity of an EV or Hybrid you can read the value of the property INFO_EV_BATTERY_CAPACITY. In a car with an electric fuel door, you can simply write true to the FUEL_DOOR_OPEN property and the VHAL should make sure that the fuel door opens. The properties that Android applications can consume are defined in the VehiclePropertyIds class, and its contract can be found under <vhalroot>/2.0/types.hal.
In order to support this property system the default android implementation contains a class called VehiclePropertyStore which can be found under:
- <vhalroot>/2.0/default/common/include/vhal_v2_0/VehiclePropertyStore.h
- <vhalroot>/2.0/default/common/src/VehiclePropertyStore.cpp
The class implements a thread-safe key-value store used to store the properties (for testing) as well as other useful debug information.
The VehicleHalManager found under <vhalroot>/2.0/default/common/src/vhal_v2_0/VehicleHalManager.cpp, is the bridge between manage and unmanaged code, it delegates most of the requests to the rest of the system, in this particular case to EmulatedVehicleHal, but it also handles the property subscription system, assisted by the hal implementation.
The EmulatedVehicleConnector and VehicleEmulator are just interfaces to communicate with the VHAL, their purpose is to open up ways of interacting with the VHAL using command tools such as lshal.
Finally, the EmulatedVehicleHal implements the basic functionality for an emulated VHAL, reading and writing properties into the VehiclePropertyStore on the get/set functions, and handling the activation of Continuous properties subscriptions.
All the above elements are a lot more than what we described here, but the “surface level” description that we did here is enough to understand the basics and tackle what we really want, play with the system!
Adding a new Custom (aka Vendor) Property
VHAL properties can be of a multitude of types, have access control, and even have different values to different areas of the car, Google documentation explains everything one needs to know about VHAL properties. What they don’t explain is how to actually add one to the system, in order to do that open the DefaultConfig.h file under:
<vhalroot>/2.0/default/impl/vhal_v2_0/DefaultConfig.h
If you scroll down you will find the kVehicleProperties definition:
const ConfigDeclaration kVehicleProperties[]{
.....
}
And here you will see the definitions of some of the supported Android Automotive VHAL properties;
{.config =
{
.prop = toInt(VehicleProperty::INFO_MAKE),
.access = VehiclePropertyAccess::READ,
.changeMode = VehiclePropertyChangeMode::STATIC,
},
.initialValue = {.stringValue = "Toy Vehicle"}},
The definitions of VehiclePropertyAccess, VehiclePropertyChangeMode, VehiclePropValue, etc are on types.h under:
# on the sources root
cd device\google\trout\hal\vehicle\2.0\agl_build\prebuilt\include\android\hardware\automotive\vehicle\2.0\
Now let's assume that your device has a property that isn’t yet supported by Android or simply something unique to your target device, you can of course add new properties. Let’s assume that the property you want to add is an int property that anyone can read and write to, and it’s a generic property not associated with any particular car area:
#define VENDOR_WRITE_PROP (0x1000 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::INT32)
Let’s assume the name of the property is VENDOR_WRITE_PROP, the above definition specifies the actual property id, by doing a composition of:
- For the unique property id, in our case, we choose 0x1000 to be far enough from the system android properties ids and make it “feature proof” avoiding any future collisions with new android properties on more recent versions.
- The property is unique to our system, so we mark it as a vendor property (VehiclePropertyGroup::VENDOR), this restricts access to this property by non-privileged applications.
- The property is a global one not associated with any particular area, so we mark it as VehicleArea::GLOBAL.
- Finally, we want an int property so we use VehiclePropertyType::INT32 to mark that.
Now with a property id defined, we need to add it to the property list kVehicleProperties, if we want the property listed alongside the rest of the system properties:
#define VENDOR_WRITE_PROP (0x1000 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::INT32)
const ConfigDeclaration kVehicleProperties[]{
{
.config = {
.prop = VENDOR_WRITE_PROP,
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE
},
.initialValue = {.int32Values = {123}}},
//other props
}
It’s in this definition that we set how the property access permissions, read and write for this particular property, and how we want to handle property subscriptions, on this situation we choose ON_CHANGE meaning that the VHAL will trigger an event to anyone that subscribed to this property when its value changes. There are other fields that you can set in here. Finally, we set the default value, since it’s an int property we set the .int32values to be an array with 1 element set to 123.
What we’ve done here is to set up a new property that automatically will be registered to the VHAL on boot, and accessible through the VehiclePropertyStore, which is used by the system to read/write properties.
That means that without any other change, you can test it out right now! So let's make a quick build:
# on the sources root
source build/envsetup.sh
lunch my_car_emul-userdebug
make
emulator -no-snapshot
By default, there isn’t any my_car_emul-userdebug** you can read it in our previous post on how to add a custom build option, or simply use aosp_car_x86-userdebug**.
Now open another terminal on your machine and run:
adb shell dumpsys car_service get-property-value 21401000
21401000 is the hex value of the integer created with the VENDOR_WRITE_PROP define
And you should see something like:
Property:0x21401000,status: 0,timestamp:99296752095,zone:0x0,floatValues: [],int32Values: [123],int64Values: [],bytes: [],string:
You can also use the Kitchen Sink application on the emulator to read and write the property, to do that on the emulator, open the app, click on Property Test, and search for 0x21401000.
Now you know how to add new properties to the VHAL, but what if you don’t want to use the included property store, and just want to talk directly to your device ECU?
Custom VHAL implementation
Before tackling the implementation, it’s important to say that the following suggestion is a quick and dirty one, an actual VHAL shouldn’t include easy ways of interacting with ECU, and most if not all of the debug features present in the proposed VHAL should be removed or disabled.
For our custom implementation, we will take advantage of the entire system left by google in the AOSP, so we will simply implement a new version of the EmulatedVehicleHal with the methods that we need in order to have full access to the system, with the minimum worries on property subscription and debug features. By simply deriving from EmulatedVehicleHal we maintain all the debug options with minimal intervention in the system.
First things first let's create two new files, VendorVehicleHal.h and VendorVehicleHal.cpp under:
# on the sources root
cd hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0
Now edit the Android.bp file under
# on the sources root
cd hardware/interfaces/automotive/vehicle/2.0/default/
Find the android.hardware.automotive.vehicle@2.0-default-impl-lib and add our new file to the sources, it should look something like:
....
cc_library_static {
name: "android.hardware.automotive.vehicle@2.0-default-impl-lib",
vendor: true,
defaults: ["vhal_v2_0_target_defaults"],
srcs: [
"impl/vhal_v2_0/CommConn.cpp",
"impl/vhal_v2_0/EmulatedVehicleConnector.cpp",
"impl/vhal_v2_0/EmulatedVehicleHal.cpp",
"impl/vhal_v2_0/VehicleHalClient.cpp",
"impl/vhal_v2_0/VehicleHalServer.cpp",
"impl/vhal_v2_0/VehicleEmulator.cpp",
"impl/vhal_v2_0/PipeComm.cpp",
"impl/vhal_v2_0/ProtoMessageConverter.cpp",
"impl/vhal_v2_0/SocketComm.cpp",
"impl/vhal_v2_0/LinearFakeValueGenerator.cpp",
"impl/vhal_v2_0/JsonFakeValueGenerator.cpp",
"impl/vhal_v2_0/GeneratorHub.cpp",
"impl/vhal_v2_0/VendorVehicleHal.cpp",
],
....
With this modification, we added our new file to the build system of the VHAL.
In the VendorVehicleHal.h copy the following:
#ifndef android_hardware_automotive_vehicle_V2_0_impl_VendorVehicleHal_H_
#define android_hardware_automotive_vehicle_V2_0_impl_VendorVehicleHal_H_
#include "EmulatedVehicleHal.h"
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
namespace V2_0 {
namespace impl {
/** Implementation of VehicleHal that connected to emulator instead of real vehicle network. */
class VendorVehicleHal : public EmulatedVehicleHal {
public:
VendorVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client,
EmulatedUserHal* emulatedUserHal = nullptr);
VehiclePropValuePtr get(const VehiclePropValue& requestedPropValue,
StatusCode* outStatus) override;
StatusCode set(const VehiclePropValue& propValue) override;
};
} // impl
} // namespace V2_0
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android
#endif // android_hardware_automotive_vehicle_V2_0_impl_EmulatedVehicleHal_H_
What we are doing here is redefining the get and set methods, so that when the VehicleHalManager calls these hal methods our code will be called.
In the VendorVehicleHal.cpp place the following:
#define LOG_TAG "VendorVehicleHal_v2_0"
#include <android-base/logging.h>
#include <android/log.h>
#include <android-base/macros.h>
#include "VendorVehicleHal.h"
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
namespace V2_0 {
namespace impl {
constexpr int INFO_EV_BATTERY_CAPACITY= (int)VehicleProperty::INFO_EV_BATTERY_CAPACITY;
VendorVehicleHal::VendorVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client,
EmulatedUserHal* emulatedUserHal)
: EmulatedVehicleHal(propStore, client, emulatedUserHal),
{
}
static int _vendorWriteProp = -1;
VehicleHal::VehiclePropValuePtr VendorVehicleHal::get(
const VehiclePropValue& requestedPropValue, StatusCode* outStatus) {
VehiclePropValuePtr v = nullptr;
ALOGI("VendorVehicleHal::get propId: 0x%x", requestedPropValue.prop);
switch(requestedPropValue.prop)
{
case VENDOR_WRITE_PROP:{
auto propValue = new VehiclePropValue();
propValue->prop = VENDOR_WRITE_PROP;
propValue->timestamp = elapsedRealtimeNano();
propValue->value.int32Values.resize(1);
propValue->value.int32Values[0] = _vendorWriteProp;
v = getValuePool()->obtain(*propValue);
ALOGI("VENDOR_WRITE_PROP calling ECU: to get its val to %d", _vendorWriteProp);
return v;
}
case INFO_EV_BATTERY_CAPACITY:{
auto propValue = new VehiclePropValue();
propValue->prop = INFO_EV_BATTERY_CAPACITY;
propValue->timestamp = elapsedRealtimeNano();
propValue->value.floatValues.resize(1);
propValue->value.floatValues[0] = 12000;
v = getValuePool()->obtain(*propValue);
ALOGI("INFO_EV_BATTERY_CAPACITY calling ECU: to get its val to %f", propValue->value.floatValues[0]);
return v;
}
}
return EmulatedVehicleHal::get(requestedPropValue, outStatus);
}
StatusCode VendorVehicleHal::set(const VehiclePropValue& propValue) {
ALOGI("VendorVehicleHal::set propId: 0x%x", propValue.prop);
switch(propValue.prop)
{
//
// Do not return in order to keep prop store updated
//
case VENDOR_WRITE_PROP:
ALOGI("VENDOR_WRITE_PROP calling ECU: change val to %f", propValue.value.floatValues[0]);
_vendorWriteProp = propValue.value.int32Values[0];
break;
}
return EmulatedVehicleHal::set(propValue);
}
} // impl
} // namespace V2_0
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android
So in our class constructor, we simply delegate to the base class the necessary values.
In the get method, you can see how to return a vendor property and a native VHAL property, in this case, the INFO_EV_BATTERY_CAPACITY is a good match since it’s a static property that only allows reads. Although we are using a global variable to emulate the ECU call on both the get and set methods you can simply dispatch the request to your ECU via IPC or directly if you wish (you now know how to add sources to the VHAL). For the properties we don’t want to handle we simply call the base implementation (EmulatedVehicleHal::get).
On the set method we do exactly the same thing, process the properties that we want and delegate the ones we don’t want to deal with to the default implementation. You probably noticed that we call the base method even if the property we are dealing with is one we want to process, this is simply to maintain the property store with the latest values in order to be able to query them using for example the adb command shown above.
Now, what if you want to periodically get information from your ECU instead of requesting custom properties on demand? How do you handle any property subscription? For testing purposes, let's create a routine that is called every 100 milliseconds using the timer included in the VHAL to call the following method:
void VendorVehicleHal::onTimer()
{
_vendorWriteProp++;
auto propValue = new VehiclePropValue();
propValue->prop = VENDOR_WRITE_PROP;
propValue->timestamp = elapsedRealtimeNano();
propValue->value.int32Values.resize(1);
propValue->value.int32Values[0] = _vendorWriteProp;
EmulatedVehicleHal::setPropertyFromVehicle(*propValue);
ALOGI("Making periodic ECU call to update VENDOR_WRITE_PROP to -> %d", _vendorWriteProp);
}
Again, where we increment the vendorWriteProp, there should be an ECU communication of some kind in order to get the actual information.
The focus point of this method is the call to setPropertyFromVehicle. This is important because besides updating the property store in the hal, it also triggers an event to any subscriber for the VENDOR_WRITE_PROP with its new value. In our case, if you still recall is exactly how we configure it, using the VehiclePropertyChangeMode::ON_CHANGE in its configuration setup.
You can find the full VendorVehicleHal.h and VendorVehicleHal.cpp sources in our github.
The last change we need to do is also related to property subscriptions, namely continuous properties (VehiclePropertyChangeMode::CONTINUOUS), which are properties whose values need to be updated on a periodic basis. A good example of this is the PERF_VEHICLE_SPEED which should indicate the current instant speed of the vehicle.
The default implementation on the EmulatedVehicleHal for the continuous properties assumes that the property store always has the latest value of a given property, but with our VendorHal that isn’t necessarily true since we can bypass the property store completely (like we did for the INFO_EV_BATTERY_CAPACITY).
So in order to fix that possible issue, we need to change the method that the EmulatedVehicleHal calls periodically in order to update continuous properties:
//located in:
//hardware/interfaces/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp
void EmulatedVehicleHal::onContinuousPropertyTimer(const std::vector<int32_t>& properties) {
VehiclePropValuePtr v;
auto& pool = *getValuePool();
auto propRequest = new VehiclePropValue();
StatusCode getRes;
for (int32_t property : properties) {
if (isContinuousProperty(property)) {
propRequest->prop = property;
VehiclePropValuePtr propValue = get(*propRequest, &getRes);
//auto propValue= mPropStore->readValueOrNull(property);
if(propValue != nullptr){
v = pool.obtain(*propValue);
}
} else {
ALOGE("Unexpected onContinuousPropertyTimer for property: 0x%x", property);
}
if (v.get()) {
v->timestamp = elapsedRealtimeNano();
doHalEvent(std::move(v));
}
}
delete propRequest;
}
The only change we made was replacing the property store get for the hal get. Since we overrode the get method, our code will be called every time a continuous property needs to be updated.
The final step we need to do in order to put our VHAL to the test is to instantiate it. In order to do that simply change the VehicleService.cpp file to instantiate our VHAL instead of the default one:
//located in
//hardware/interfaces/automotive/vehicle/2.0/default/VehicleService.cpp
....
int main(int /* argc */, char* /* argv */ []) {
auto store = std::make_unique<VehiclePropertyStore>();
auto connector = std::make_unique<impl::EmulatedVehicleConnector>();
//our hal
auto hal = std::make_unique<impl::VendorVehicleHal>(store.get(), connector.get(), nullptr);
auto emulator = std::make_unique<impl::VehicleEmulator>(hal.get());
auto service = std::make_unique<VehicleHalManager>(hal.get());
connector->setValuePool(hal->getValuePool());
....
And you are set! You have full control over the system VHAL and can integrate it with whatever you wish for your device.
Final Remarks
With what we just described you now have a basic understanding of how the Android Automotive VHAL works; knowledge of how to add new properties to Android Automotive VHAL; have the tools to edit the proposed implementation and use it to your advantage in order to quickly prototype communication with your ECU.
In the next post we will tackle the application side of things, how can we consume our properties in a managed application, generate the new car library (android.car.jar) with our vendor properties, and develop all of this on Android Studio.
메타데이터
- post_id
- 483aeddbf25b
- slug
- android-automotive-3-vehicle-hal-483aeddbf25b
- url
- https://medium.com/@imaginationoverflow/android-automotive-3-vehicle-hal-483aeddbf25b
- canonical_url
- https://medium.com/@imaginationoverflow/android-automotive-3-vehicle-hal-483aeddbf25b
- author_url
- https://medium.com/@imaginationoverflow
- status
- ok
- fetched_at
- 2026-06-29 01:02:39