Сервис настройки и калибровки (ConCal) на платформе программно-определяемого автомобиля (SDV) позволяет настраивать сервисы SDV в соответствии со спецификациями автомобиля, нормативными требованиями страны и функциями, заказанными клиентом. Этот сервис – фундаментальный строительный блок платформы SDV, позволяющий OEM-производителям повторно использовать один и тот же код сервиса в нескольких автомобилях, настраивая их и позволяя выполнять перенастройку из нескольких источников (например, на заводе, в сервисном центре, из облака).
Платформа SDV предоставляет API для настройки и калибровки пакетов сервисов на определенном автомобиле. С помощью этого интерфейса производители оригинального оборудования могут реализовать логику настройки и калибровки, характерную для их устройств.
Услуга по настройке и калибровке включает следующие процессы:
Конфигурация. Определение основных свойств и поведения автомобиля. Может зависеть от нескольких факторов, таких как местоположение автомобиля, заказанные пользователем опции или правила страны. Она определяет, как взаимодействуют компоненты, и задает настройки ПО, влияющие на общую функциональность автомобиля, например варианты ПО, сетевые подключения и начальные рабочие параметры.
Калибровка. Тонкая настройка параметров системы в пределах заданных диапазонов. Например, калибровка позволяет настроить точность датчиков и приводов, оптимизировать работу двигателя для контроля выбросов, а также улучшить управляемость и реакцию систем безопасности. Конфигурация задает базовую структуру функционирования транспортного средства, а калибровка оптимизирует его поведение в рамках этой структуры. Оба эти фактора имеют решающее значение для обеспечения соответствия транспортных средств нормам выбросов, максимальной производительности, повышения безопасности и могут компенсировать износ с течением времени.
Стандартный API ConCal для SDV упрощает реализацию пакетов сервисов SDV, устраняя необходимость повторно реализовывать функции настройки и калибровки для работы на разных автомобилях от разных OEM-производителей.
Архитектура
Каждый пакет сервисов может владеть одним или несколькими артефактами конфигурации.
Объекты конфигурации
Артефакт конфигурации (конфигурация) состоит из одного или нескольких параметров конфигурации и их значений. Конфигурация – это сообщение protobuf, относящееся к сервису, поля которого могут включать вложенные сообщения protobuf (структуры), карты, массивы, параметры int, float, bool, bytes или string.
// Example of a configuration message.
message SampleServiceBundleConfig
{
bool bool_parameter = 1;
int64 int_parameter = 2;
float float_parameter = 3;
string str_parameter = 4;
repeated string list_parameter = 5;
map<string, int32> map_parameter = 6;
SomeNestedMessage nested_parameter = 7;
SomeComplexMessage complex_parameter = 8;
some.nested.package.SomeNestedMessage nested_package = 9;
bytes bytes_parameter = 10;
}
Идентификатор конфигурации
У каждой конфигурации есть уникальный идентификатор. Этот идентификатор состоит из полного имени экземпляра владельца пакета сервисов и названия конфигурации. Название конфигурации должно быть человекочитаемым, уникальным для каждого пакета услуг и соответствовать стандартам именования, определенным в правилах присвоения названий пакетам услуг, например shared, private, diagnostics и calibration.
Ограничения:
- Название экземпляра должно начинаться с буквы.
- Допускаются только строчные буквы, цифры и дефисы.
- В названии не должно быть более одного дефиса подряд.
- Название конфигурации не должно заканчиваться дефисом.
- Название конфигурации не должно превышать 48 символов.
- Названия конфигураций должны быть уникальными на одной ВМ для одного пакета сервисов.
До второго квартала 2026 года идентификатор конфигурации определяется следующим образом:
// Unique identifier for the config.
message ConfigId {
// The FQIN of the service bundle that owns the configuration.
com.sdv.google.sd_common.ServiceFqin service_fqin = 1;
// The name of the config.
string config_name = 2;
}
Владелец конфигурации при загрузке знает только схему своей конфигурации и ее значения по умолчанию. Чтобы настроить поведение пакета сервисов для текущего автомобиля, пакет сервисов должен зарегистрировать свою конфигурацию по умолчанию вместе со своей схемой.
Рисунок 1. Регистрация конфигурации по умолчанию и получение пользовательской конфигурации.
Это позволяет OEM-производителям один раз реализовать пакеты сервисов и использовать их на нескольких автомобилях.
Развертывание
ConCal может поддерживать один или несколько экземпляров сервера на платформе SDV. Пакеты сервисов должны обнаруживать и использовать ближайший сервер ConCal. Например, если ConCal развернут по одному на ЭБУ, пакет сервисов должен получить доступ к экземпляру ConCal, работающему на том же ЭБУ. Это позволяет пакету сервисов своевременно получать конфигурацию. Если у экземпляра ConCal нет запрошенной конфигурации (поскольку она относится к зоне ответственности другого ConCal), сервер ConCal, к которому обратились, запрашивает ее у экземпляра ConCal, которому она принадлежит, и перенаправляет ее в пакет сервисов.
Настройка конфигурации
Возможность использовать одно и то же ПО в разных автомобилях – одно из главных преимуществ SDV. Программное обеспечение разрабатывается один раз, а затем повторно используется на нескольких транспортных средствах, и мы можем корректировать поведение программного обеспечения в зависимости от специфики транспортного средства. Это основная цель ConCal, который рассчитывает конфигурацию сервиса на основе свойств автомобиля с помощью переопределений конфигурации.
ConfigOverride – это сообщение protobuf, описывающее, как настроить конфигурацию для конкретного транспортного средства. Он состоит из идентификатора переопределения, который уникальным образом определяется предоставляющим его объектом, идентификатора конфигурации и списка ConfigOverrideKeyValuePair. ConfigOverride можно предоставить только во время обновления и только разрешенным сервисам, которые моделируются OEM-производителем. Определения protobuf для обеих структур приведены ниже.
// Key-value pair to update configuration.
message ConfigOverrideKeyValue {
string key = 1;
oneof value {
string value_txtproto = 2;
.google.protobuf.Any value_any = 3;
}
}
// A collection of changes for a specific configuration which should be atomically applied.
message ConfigOverride {
string override_id = 1;
ConfigId config_id = 2;
repeated ConfigOverrideKeyValue pairs = 3;
}
ConfigOverride поддерживает следующие операции:
Назначение нового значения. Последнее значение устаревает, и параметру назначается новое значение, например новое значение для простого поля (int, string, float, bool, bytes) или перезапись сложных полей, таких как карты, списки, структуры или полная конфигурация.
Удаление или очистка значения. Эта операция доступна для всех типов, включая сообщения, повторяющиеся поля, карты, единичные поля и саму конфигурацию. Эту операцию можно выполнить для всего поля или сообщения. Удаление определенных ключей в карте и отдельных элементов из повторяющегося поля не поддерживается.
Добавьте новое значение на карту.
Перезаписать значение существующего ключа в карте (эту операцию также можно выполнить, добавив новое значение в карту, если ключ не существует).
Как настроить пакет сервисов с помощью ConCal
В этой главе рассказывается, как разработать пакет сервисов, который получает свою конфигурацию во время выполнения. С точки зрения настраиваемого пакета совершенно неясно, является ли полученная конфигурация заводской настройкой по умолчанию или последующей модификацией с использованием процессов переопределения и калибровки ConCal.
Пример, на котором основана документация, можно найти по адресу system/software_defined_vehicle/samples/concal/src/concal_client.
Подробнее о разработке пакетов сервисов…
Объявите тип конфигурации, принадлежащей пакету сервисов.
Пакет сервисов владеет типом конфигурации, которую он получает. Это позволяет обновлять настраиваемый пакет независимо от сохраненных данных конфигурации.
Создайте файл protobuf (с расширением
.proto), в котором будет объявлен тип конфигурации:syntax = "proto3"; package android.sdv.demo.config; message RearViewCamera { string model = 1; uint64 horizontal_resolution = 2; uint64 vertical_resolution = 3; float x_axis_field_of_view = 4; float y_axis_field_of_view = 5; bool is_rgb = 6; }Создайте цель сборки, которая генерирует библиотеку времени выполнения, позволяющую получить тип конфигурации. В файле
Android.bp:rust_protobuf { name: "libsdvtestconcal_proto_rust", crate_name: "sdvtestconcal_proto_rust", protos: [ "rear_view_camera.proto", ], proto_flags: [ "-I external/protobuf/src", "-I .", ], source_stem: "sdvtestconcal_proto_rust", vendor_available: true, product_available: true, min_sdk_version: "35", }
Сгенерируйте код промежуточного ПО ConCal RPC для своего пакета.
Добавьте в пакет сервиса декларацию VSIDL:
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
Это объявляет, что пакет является клиентом сервиса регистрации и получения конфигурации ConCal. Общие сведения об использовании VSIDLC для создания привязок клиента RPC для пакета можно найти в разделе Обзор VSIDL и промежуточного ПО.
Инициализируйте промежуточное ПО для получения конфигурации RPC
Во время выполнения инициализируйте компоненты промежуточного ПО, необходимые для вызовов RPC ConCal. В этом примере мы выполняем асинхронную инициализацию при запуске пакета.
pub struct ExampleConcalBundle {
context: ContextRef,
runtime: Option<Runtime>,
}
sdv::lifecycle::register_service_bundle!(ExampleConcalBundle);
impl ServiceBundle for ExampleConcalBundle {
fn new(context: ContextRef) -> ExampleConcalBundle {
info!("Creating {}.", context.get_self_fqin());
ExampleConcalBundle { context, runtime: None }
}
fn on_start(&mut self) {
let fqin = self.context.get_self_fqin();
info!("Starting {}.", fqin);
let runtime = Builder::new_multi_thread()
.worker_threads(4)
.thread_name("tokio-pool")
.enable_all()
.build()
.unwrap();
let context = self.context;
runtime.spawn(async move {
let registration_client = setup_register_config_rpc(context).await;
/* main SB logic here */
});
self.runtime = Some(runtime);
}
async fn setup_register_config_rpc(context: ContextRef) -> RegistrationClient {
let sdv_comms = SdvComms { context };
let sd = ServiceDiscoveryManager::new(context);
let unit_name_args = UnitNameDiscoveryArgs::new_builder()
.set_sdv_package_name("com.sdv.oem.sample.concal")
.set_service_bundle_name("SampleOemConCalServiceBundle")
.set_service_unit_name(RegistrationClient::DEFAULT_UNIT_NAME)
.build()
.unwrap();
let mut unit_name_stream =
sd.subscribe_service_unit_change_by_name(&unit_name_args).await.unwrap();
// wait until RPC servers are registered, if server is not a custom agent
while let Some(event) = unit_name_stream.next().await {
if let ServiceUnitChangeEvent::Registered(sud) = event {
let service_identity = sud.get_service_bundle_identity();
let fqin = service_identity.get_fqin();
if fqin.get_sdv_package_name() == "com.sdv.oem.sample.concal"
&& fqin.get_service_bundle_name() == "SampleOemConCalServiceBundle"
&& fqin.get_service_instance_name() == "default"
{
break;
}
}
}
let service_bundle =
SampleOemConCalClientServiceBundle::new(Arc::new(sdv_comms)).await.unwrap();
service_bundle
.create_rpc_client::<RegistrationClient>(
UnitName::builder()
.package_name("com.sdv.oem.sample.concal")
.bundle_name("SampleOemConCalServiceBundle")
.service_unit_name(RegistrationClient::DEFAULT_UNIT_NAME)
.build()
.unwrap(),
ClientOptions::default(),
)
.await
.expect("Failed to create an RPC client")
}
Место:
- В
on_startмы создаем среду выполнения tokio и порождаем задачу tokio. Задача вызываетsetup_register_rpc, прежде чем перейти к основной логике пакета. setup_register_rpcнастраивает привязку RPC промежуточного ПО. Обратите внимание, что в примере не предполагается, что функциональность ConCal реализована агентом: сервер может стать доступным только после запуска пакета. Поэтому в примере кода ожидается регистрация RPC-сервера с помощью Service Discovery API.
Инициализация конфигурации
Зарегистрируйте артефакт конфигурации, предоставив серверу ConCal идентификатор, схему конфигурации и заводские значения конфигурации по умолчанию.
Если регистрация вызывается впервые, сохраняется значение по умолчанию. Если регистрация вызывается при последующих запусках пакета, будет получено сохраненное значение по умолчанию с примененными переопределениями ConCal (если они есть).
Вызов конфигурации реестра ConCal всегда выполняется успешно, независимо от того, зарегистрирован ли артефакт.
impl ServiceBundle for ExampleConcalBundle{
/* ... */
fn on_start(&mut self) {
/* ... */
runtime.spawn(async move {
let registration_client = setup_register_config_rpc(context).await;
sample_concal_main(fqin, registration_client).await
});
self.runtime = Some(runtime);
}
}
async fn sample_concal_main(
fqin: ServiceFqin,
registration_client: RegistrationClient,
) -> sdv::status::SdvResult<()> {
let config = get_rear_view_camera_factory_config();
let config_id = get_config_id(&fqin);
register_config(®istration_client, &config_id, &config).await;
/* ... */
}
fn get_rear_view_camera_factory_config() -> RearViewCamera {
RearViewCamera {
model: String::from("model 1"),
horizontal_resolution: 720,
vertical_resolution: 720,
x_axis_field_of_view: 70.0,
y_axis_field_of_view: 70.0,
is_rgb: false,
..Default::default()
}
}
fn get_config_id(fqin: &ServiceFqin) -> ConfigId {
ConfigId {
config_name: "config".to_string(),
service_fqin: MessageField::some(ProtoFqin {
vm_name: fqin.get_sdv_vm_name().to_string(),
package_name: fqin.get_sdv_package_name().to_string(),
service_name: fqin.get_service_bundle_name().to_string(),
instance_name: fqin.get_service_instance_name().to_string(),
..Default::default()
}),
..Default::default()
}
}
async fn register_config(
client: &RegistrationClient,
config_id: &ConfigId,
config: &RearViewCamera,
) {
let config_fd = FileDescriptorSet {
file: vec![RearViewCamera::descriptor().file_descriptor_proto().clone()],
..Default::default()
};
let config = Any::pack(config).expect("Failed to pack config");
let config_metadata = ConfigMetadata {
descriptor_set: MessageField::some(config_fd),
default_config: MessageField::some(config.clone()),
..Default::default()
};
client
.RegisterConfigMetadata(&RegisterConfigMetadataRequest {
config_id: MessageField::some(config_id.clone()),
metadata: MessageField::some(config_metadata),
config_version: String::from("1.0"),
..Default::default()
})
.await
.expect(
"RegisterConfigMetadata should not fail, even if configuration was registered before",
);
}
Место:
- Задача, созданная в
on_start, после получения привязки RPC продолжает работу с бизнес-логикой, вызываяsample_concal_main. sample_concal_mainначинается с регистрации артефакта конфигурации. Логика содержится вregister_config.- Чтобы зарегистрировать конфигурацию, пакет должен указать тип protobuf, идентификатор и значение по умолчанию.
config_fd– тип конфигурации. Пакет, которому принадлежит тип конфигурации, гарантирует, что схема, ожидаемая пакетом, всегда извлекается, в том числе после обновлений APEX.- Идентификатор используется в качестве идентификатора во внутренней логике сохранения сервера ConCal.
- Значение по умолчанию создается в
get_rear_view_camera_factory_config. Значение по умолчанию – это значение, сохраненное сервером ConCal, если ранее ничего не сохранялось. Это один из способов, с помощью которых система может указывать заводские конфигурации. Возможны и другие варианты.
Получение конфигурации
После регистрации получите конфигурацию. Поскольку конфигурация была зарегистрирована ранее, этот вызов гарантированно будет успешным.
async fn sample_concal_main(
fqin: ServiceFqin,
registration_client: RegistrationClient,
update_client: UpdateClient,
) -> sdv::status::SdvResult<()> {
let config = get_rear_view_camera_factory_config();
let config_id = get_config_id(&fqin);
register_config(®istration_client, &config_id, &config).await;
let config = get_config(®istration_client, &config_id).await;
info!("Retrieved configuration:\n{config:#?}");
// Onwards, use configuration in bundle's main business logic
/* ... */
}
async fn get_config(client: &RegistrationClient, config_id: &ConfigId) -> RearViewCamera {
let bytes = client
.GetConfig(&GetConfigRequest {
config_id: MessageField::some(config_id.clone()),
..Default::default()
})
.await
.expect("Get config does not fail, as config was registered before")
.config;
RearViewCamera::parse_from_bytes(&bytes).expect("parse_from_bytes failed")
}
Место:
Вызов
GetConfigRequestгарантированно будет успешным, поскольку конфигурация была зарегистрирована ранее тем же пакетом. Настройка позволяет пакету сервисов обрабатывать оба случая (заводские и переопределенные конфигурации) без изменения бизнес-логики.Вызов
GetConfigRequestвозвращает необработанные байты. Функцияget_configпреобразует их в ожидаемый тип конфигурации.