O serviço de configuração e calibragem (ConCal, na sigla em inglês) na plataforma de veículo definido por software (SDV, na sigla em inglês) oferece recursos para configurar serviços de SDV de acordo com as especificações do veículo, os regulamentos do país e os recursos encomendados pelo cliente. Esse serviço é um bloco de construção fundamental da plataforma SDV, permitindo que os OEMs reutilizem o mesmo código de serviço em vários veículos, configurando-os e ativando a reconfiguração de várias fontes (por exemplo, em uma fábrica, em uma oficina, na nuvem).
A plataforma SDV fornece APIs voltadas para o serviço para configuração e calibragem de pacotes de serviços em um veículo específico. Usando essa interface, os OEMs podem implementar uma lógica de configuração e calibragem específica do OEM.
O serviço de configuração e calibragem inclui os seguintes processos:
Configuração, que envolve a definição das propriedades e do comportamento fundamentais de um veículo e pode depender de vários fatores, como a localização do veículo, as opções encomendadas pelo usuário ou os regulamentos do país. Ela estabelece como os componentes interagem e determina as configurações de software que influenciam a funcionalidade geral do veículo, como variantes de software, conexões de rede e parâmetros operacionais iniciais.
Calibragem, que ajusta os parâmetros do sistema dentro dos intervalos pré-configurados. Por exemplo, a calibragem ajusta a precisão do sensor e do atuador, otimiza o desempenho do motor para o controle de emissões e refina a capacidade de direção e as respostas do sistema de segurança. A configuração define a estrutura básica de como um veículo funciona, enquanto a calibragem otimiza o comportamento dele nessa estrutura. Ambos são essenciais para garantir que os veículos atendam aos regulamentos de emissões, maximizem o desempenho, melhorem a segurança e possam compensar o desgaste ao longo do tempo.
Ao fornecer uma API ConCal padrão em toda a SDV, simplificamos a implementação de pacotes de serviços de SDV, evitando a necessidade de reimplementar os recursos de configuração e calibragem para serem executados em diferentes veículos de diferentes OEMs.
Arquitetura
Cada pacote de serviços pode ter um ou mais artefatos de configuração.
Artefatos de configuração
Um artefato de configuração (config) consiste em um ou mais parâmetros de configuração e os valores deles. A configuração é uma mensagem protobuf específica do serviço cujos campos podem incluir mensagens protobuf aninhadas (struct), mapas, matrizes, parâmetros int, float, bool, bytes ou 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;
}
Identificador de configuração
Cada configuração tem um identificador exclusivo. Esse identificador consiste no nome da instância totalmente qualificado do proprietário do pacote de serviços e em um nome de configuração. Um nome de configuração precisa ser legível, exclusivo por pacote de serviços,
e seguir os padrões de nomenclatura definidos em
Convenções de nomenclatura de pacotes de serviços,
como shared, private, diagnostics, e calibration.
Restrições:
- O nome da instância precisa começar com uma letra.
- Todos os caracteres precisam ser alfanuméricos minúsculos ou um hífen.
- Os hifens no nome não podem aparecer consecutivamente mais de uma vez.
- O nome da configuração não pode terminar com um hífen.
- O nome da configuração não pode ter mais de 48 caracteres.
- Os nomes de configuração precisam ser exclusivos na mesma VM para o mesmo pacote de serviços.
Antes da 26Q2, o ID de configuração era definido da seguinte maneira:
// 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;
}
O proprietário da configuração na inicialização só conhece o esquema da configuração e os valores padrão. Para personalizar o comportamento do pacote de serviços em um veículo atual, o pacote de serviços proprietário precisa registrar a configuração padrão com o esquema.
Figura 1. Registro da configuração padrão e recuperação da configuração personalizada.
Isso permite que os OEMs implementem pacotes de serviços uma vez e os executem em vários veículos.
Implantação
O ConCal pode manter uma ou mais instâncias de servidor na plataforma SDV. Os pacotes de serviços precisam descobrir e usar o servidor ConCal mais próximo. Por exemplo, se o ConCal for implantado um por ECU, um pacote de serviços precisará ter acesso à instância do ConCal em execução na mesma ECU. Isso permite que o pacote de serviços recupere a configuração de maneira oportuna. Se a instância do ConCal não tiver uma configuração solicitada (porque pertence à área de responsabilidade de outro ConCal), o servidor ConCal contatado a solicitará da instância do ConCal proprietária e a redirecionará para o pacote de serviços.
Personalização da configuração
A capacidade de reutilizar o mesmo software com veículos diferentes é uma das principais vantagens da SDV. O software é desenvolvido uma vez e reutilizado em vários veículos, e podemos ajustar o comportamento do software com base nas especificidades do veículo. Esse é o principal objetivo do ConCal, que calcula a configuração do serviço com base nas propriedades do veículo com a ajuda de substituições de configuração.
ConfigOverride é uma mensagem protobuf que descreve como ajustar a configuração para o veículo específico. Ela consiste em um ID de substituição, que é definido exclusivamente pela entidade que o fornece, um identificador de configuração e uma lista de ConfigOverrideKeyValuePair. ConfigOverride só pode ser fornecido durante o processo de atualização e apenas pelos serviços permitidos, que são modelados pelo OEM. As definições de protobuf para ambas as estruturas são fornecidas abaixo.
// 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 aceita estas operações:
Atribuir um novo valor: o último valor é descontinuado e o parâmetro recebe um novo valor, como atribuir um novo valor a um campo simples (int, string, float, bool, bytes) ou reescrever campos complexos, como mapas, listas, structs ou uma configuração completa.
Remover ou limpar um valor: essa operação é fornecida para todos os tipos, incluindo mensagens, campos repetidos, mapas, campos únicos e a própria configuração. Essa operação pode ser realizada em um campo ou mensagem completo, o que significa que a remoção de chaves específicas em um mapa e elementos individuais de um campo repetido não é aceita.
Adicionar um novo valor a um mapa.
Reescrever o valor de uma chave existente em um mapa. Essa operação também pode ser realizada como a adição de um novo valor a um mapa, se a chave não existir.
Configurar um pacote de serviços usando o ConCal
Este capítulo descreve como desenvolver um pacote de serviços que recupera a configuração no ambiente de execução. Do ponto de vista de um pacote configurável, é completamente opaco se a configuração recuperada é uma configuração de fábrica padrão ou uma modificação subsequente usando processos de substituição e calibragem do ConCal.
O exemplo em que a documentação se baseia pode ser encontrado em system/software_defined_vehicle/samples/concal/src/concal_client.
Consulte Desenvolvimento de pacotes de serviços para mais detalhes.
Declarar o tipo de configuração de propriedade do pacote de serviços
O pacote de serviços é proprietário do tipo de configuração que ele recupera. Isso permite que um pacote configurável seja atualizado de forma independente dos dados de configuração persistentes.
Grave um arquivo protobuf (com uma extensão
.proto) declarando o tipo de configuração: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; }Crie um destino de build que gere uma biblioteca de ambiente de execução permitindo a recuperação do tipo de configuração. Em um arquivo
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", }
Gerar código de middleware RPC do ConCal para seu pacote
Adicione uma declaração VSIDL ao pacote de serviços:
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
Isso declara que o pacote é um cliente do serviço de registro e recuperação de configuração do ConCal. Consulte Visão geral do VSIDL e do middleware para uma visão geral do uso do VSIDLC para gerar vinculações de cliente RPC para o pacote.
Inicializar o middleware para recuperação de configuração RPC
No ambiente de execução, inicialize os componentes de middleware necessários para chamadas RPC do ConCal. Neste exemplo, inicializamos de forma assíncrona quando o pacote é iniciado.
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")
}
Em que:
- Em
on_start, criamos um ambiente de execução do tokio e geramos uma tarefa do tokio. A tarefa chamasetup_register_rpcantes de prosseguir com a lógica principal do pacote. setup_register_rpcconfigura a vinculação RPC do middleware. O exemplo não pressupõe que a funcionalidade do ConCal seja implementada por um agente: o servidor poderá ficar disponível somente depois que o pacote for iniciado. Portanto, o código de exemplo aguarda o registro do servidor RPC usando a API Service Discovery.
Inicializar configuração
Registre o artefato de configuração fornecendo ao servidor ConCal um ID, o esquema de configuração e os valores de configuração padrão de fábrica.
Se o registro for chamado pela primeira vez, o valor padrão será mantido. Se o registro for chamado em inicializações de pacote subsequentes, o valor padrão persistente com substituições do ConCal aplicadas (se houver) será recuperado.
A chamada de configuração de registro do ConCal nunca falha, independentemente de o artefato já estar registrado.
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",
);
}
Em que:
- A tarefa gerada em
on_start, depois de recuperar a vinculação RPC, prossegue com a lógica de negócios, chamandosample_concal_main. sample_concal_maincomeça registrando um artefato de configuração. A lógica está contida emregister_config.- Para registrar uma configuração, um pacote precisa especificar o tipo protobuf, um ID e um valor padrão.
config_fdé o tipo de configuração. O pacote proprietário do tipo de configuração garante que o esquema esperado pelo pacote seja sempre recuperado, incluindo atualizações pós-APEX.- O ID é usado como um identificador na lógica de persistência interna do servidor ConCal.
- O valor padrão é construído em
get_rear_view_camera_factory_config. O valor padrão é o valor mantido pelo servidor ConCal, se nada foi mantido antes. Isso representa uma das maneiras pelas quais o sistema pode especificar configurações de fábrica. Outras configurações são possíveis.
Recuperar configuração
Após o registro, recupere a configuração. Como a configuração foi registrada antes, essa chamada tem garantia de sucesso.
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")
}
Em que:
A chamada para
GetConfigRequesttem garantia de sucesso, já que a configuração foi registrada antes, pelo mesmo pacote. A configuração permite que o pacote de serviços processe de forma opaca os casos de configurações de fábrica e configurações substituídas: a lógica de negócios do pacote de serviços permanece inalterada.A chamada
GetConfigRequestretorna bytes brutos. A funçãoget_configprossegue com a análise deles para o tipo de configuração esperado.