Il servizio di configurazione e calibrazione (ConCal) sulla piattaforma software-defined vehicle (SDV) fornisce funzionalità per configurare i servizi SDV in base alle specifiche del veicolo, alle normative del paese e alle funzionalità ordinate dal cliente. Questo servizio è un elemento fondamentale della piattaforma SDV, che consente agli OEM di riutilizzare lo stesso codice di servizio su più veicoli configurandoli e consentendo la riconfigurazione da più origini (ad esempio, in una fabbrica, in un centro assistenza, dal cloud).
La piattaforma SDV fornisce API orientate ai servizi per la configurazione e la calibrazione dei bundle di servizi su un veicolo specifico. Utilizzando questa interfaccia, gli OEM possono implementare la logica di configurazione e calibrazione specifica per OEM.
Il servizio di configurazione e calibrazione include i seguenti processi:
Configurazione, che prevede la definizione delle proprietà e del comportamento fondamentali di un veicolo e potrebbe dipendere da più fattori, come la posizione del veicolo, le opzioni ordinate dall'utente o le normative del paese. Stabilisce il modo in cui i componenti interagiscono e determina le impostazioni software che influenzano la funzionalità complessiva del veicolo, come varianti software, connessioni di rete e parametri operativi iniziali.
Calibrazione, che perfeziona i parametri di sistema all'interno degli intervalli preconfigurati. Ad esempio, la calibrazione regola l'accuratezza di sensori e attuatori, ottimizza le prestazioni del motore per il controllo delle emissioni e perfeziona la guidabilità e le risposte del sistema di sicurezza. La configurazione imposta il framework di base per il funzionamento di un veicolo, mentre la calibrazione ne ottimizza il comportamento all'interno di questo framework. Entrambi sono fondamentali per garantire che i veicoli rispettino le normative sulle emissioni, massimizzino le prestazioni, migliorino la sicurezza e possano compensare l'usura nel tempo.
Fornendo un'API ConCal standard a livello di SDV, semplifichiamo l'implementazione di pacchetti di servizi SDV, evitando la necessità di reimplementare le funzionalità di configurazione e calibrazione per l'esecuzione su veicoli diversi di diversi OEM.
Architettura
Ogni pacchetto di servizi può possedere uno o più artefatti di configurazione.
Artefatti di configurazione
Un artefatto di configurazione (config) è costituito da uno o più parametri di configurazione e dai relativi valori. La configurazione è un messaggio protobuf specifico del servizio i cui campi possono includere messaggi protobuf nidificati (struct), mappe, array, parametri int, float, bool, byte o stringa.
// 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;
}
Identificatore di configurazione
Ogni configurazione ha un identificatore univoco. Questo identificatore è costituito dal nome dell'istanza completamente qualificato del proprietario del bundle di servizi e da un nome di configurazione. Un nome di configurazione deve essere leggibile, univoco per bundle di servizi e seguire gli standard di denominazione definiti in Convenzioni di denominazione dei bundle di servizi, ad esempio shared, private, diagnostics e calibration.
Restrizioni:
- Il nome dell'istanza deve iniziare con una lettera.
- Tutti i caratteri devono essere alfanumerici minuscoli o un trattino.
- I trattini nel nome non devono apparire consecutivamente più di una volta.
- Il nome della configurazione non deve terminare con un trattino.
- Il nome della configurazione non deve contenere più di 48 caratteri.
- I nomi delle configurazioni devono essere univoci sulla stessa VM per lo stesso bundle di servizi.
Prima del 26Q2, l'ID configurazione è definito come segue:
// 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;
}
Il proprietario della configurazione all'avvio conosce solo lo schema della configurazione e i relativi valori predefiniti. Per personalizzare il comportamento del service bundle in base a un veicolo attuale, il service bundle proprietario deve registrare la sua configurazione predefinita insieme al suo schema.
Figura 1. Registrazione della configurazione predefinita e recupero della configurazione personalizzata.
In questo modo, gli OEM possono implementare i bundle di servizi una sola volta ed eseguirli su più veicoli.
Deployment
ConCal può gestire una o più istanze del server sulla piattaforma SDV. I bundle di servizi devono rilevare e utilizzare il server ConCal più vicino. Ad esempio, se ConCal viene implementato uno per ECU, un pacchetto di servizi deve accedere all'istanza ConCal in esecuzione sulla stessa ECU. In questo modo, il bundle di servizi può recuperare la configurazione in modo tempestivo. Se l'istanza ConCal non ha una configurazione richiesta (in quanto appartiene all'area di responsabilità di un altro ConCal), il server ConCal contattato la richiede all'istanza ConCal proprietaria e la reindirizza al bundle di servizi.
Personalizzazione della configurazione
La possibilità di riutilizzare lo stesso software con veicoli diversi è uno dei principali vantaggi delle SDV. Il software viene sviluppato una sola volta e poi riutilizzato su più veicoli. Possiamo regolarne il comportamento in base alle specifiche del veicolo. Questo è lo scopo principale di ConCal, che calcola la configurazione del servizio in base alle proprietà del veicolo con l'aiuto degli override di configurazione.
ConfigOverride è un messaggio protobuf che descrive come modificare la
configurazione per il veicolo specifico. È composto da un ID override, definito in modo univoco dall'entità che lo fornisce, da un identificatore di configurazione e da un elenco di ConfigOverrideKeyValuePair. ConfigOverride può essere fornito solo
durante la procedura di aggiornamento e solo dai servizi consentiti, modellati
dall'OEM. Di seguito sono riportate le definizioni di protobuf per entrambe le strutture.
// 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 supporta le seguenti operazioni:
Assegna un nuovo valore: l'ultimo valore è ritirato e al parametro viene assegnato un nuovo valore, ad esempio l'assegnazione di un nuovo valore a un campo semplice (int, stringa, float, bool, byte) o la riscrittura di campi complessi, come mappe, elenchi, struct o una configurazione completa.
Rimuovi o pulisci un valore: questa operazione è fornita per tutti i tipi, inclusi messaggi, campi ripetuti, mappe, campi singoli e la configurazione stessa. Questa operazione può essere eseguita su un campo o un messaggio completo, il che significa che la rimozione di chiavi specifiche in una mappa e di singoli elementi da un campo ripetuto non è supportata.
Aggiungere un nuovo valore a una mappa.
Riscrivi il valore di una chiave esistente in una mappa (questa operazione può essere eseguita anche come aggiunta di un nuovo valore a una mappa, se la chiave non esiste).
Configurare un bundle di servizi utilizzando ConCal
Questo capitolo descrive come sviluppare un bundle di servizi che recupera la sua configurazione in fase di runtime. Dal punto di vista di un bundle configurabile, non è possibile determinare se la configurazione recuperata è un'impostazione predefinita di fabbrica o una modifica successiva utilizzando i processi di override e calibrazione di ConCal.
Puoi trovare l'esempio su cui si basa la documentazione all'indirizzo
system/software_defined_vehicle/samples/concal/src/concal_client.
Per maggiori dettagli, consulta Sviluppo di bundle di servizi.
Dichiara il tipo di configurazione di proprietà del bundle di servizi
Il pacchetto di servizi è proprietario del tipo di configurazione che recupera. Ciò consente di aggiornare un bundle configurabile indipendentemente dai dati di configurazione persistenti.
Scrivi un file protobuf (con estensione
.proto) che dichiara il tipo di configurazione: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; }Crea un target di compilazione che generi una libreria runtime che consenta il recupero del tipo di configurazione. In un file
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", }
Genera il codice middleware RPC ConCal per il tuo bundle
Aggiungi una dichiarazione VSIDL al bundle di servizi:
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
Dichiara che il bundle è un client del servizio di registrazione e recupero della configurazione ConCal. Consulta Panoramica di VSIDL e middleware per una panoramica dell'utilizzo di VSIDLC per generare binding client RPC per il bundle.
Inizializza il middleware per il recupero della configurazione RPC
In fase di runtime, inizializza i componenti middleware richiesti per le chiamate RPC ConCal. In questo esempio, l'inizializzazione asincrona viene eseguita all'avvio del bundle.
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")
}
Dove:
- In
on_start, creiamo un runtime tokio e generiamo un'attività tokio. L'attività chiamasetup_register_rpcprima di procedere con la logica principale del bundle. setup_register_rpcconfigura l'associazione RPC del middleware. Tieni presente che l'esempio non presuppone che la funzionalità ConCal sia implementata da un agente: il server potrebbe diventare disponibile solo dopo l'avvio del bundle. Pertanto, il codice di esempio attende la registrazione del server RPC utilizzando l'API Service Discovery.
Inizializza configurazione
Registra l'artefatto di configurazione fornendo al server ConCal un ID, lo schema di configurazione e i valori di configurazione predefiniti di fabbrica.
Se la registrazione viene chiamata per la prima volta, il valore predefinito viene mantenuto. Se la registrazione viene chiamata all'avvio dei bundle successivi, verrà recuperato il valore predefinito persistente con le eventuali sostituzioni ConCal applicate.
La chiamata di configurazione del registro ConCal non ha mai esito negativo, indipendentemente dal fatto che l'artefatto sia già registrato.
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",
);
}
Dove:
- L'attività generata in
on_start, dopo aver recuperato l'associazione RPC, procede con la logica di business, chiamandosample_concal_main. sample_concal_maininizia registrando un artefatto di configurazione. La logica è contenuta inregister_config.- Per registrare una configurazione, un bundle deve specificare il tipo protobuf, un ID e un valore predefinito.
config_fdè il tipo di configurazione. Il bundle proprietario del tipo di configurazione garantisce che lo schema previsto dal bundle venga sempre recuperato, inclusi gli aggiornamenti post-APEX.- L'ID viene utilizzato come identificatore nella logica di persistenza interna del server ConCal.
- il valore predefinito viene creato in
get_rear_view_camera_factory_config. Il valore predefinito è il valore reso persistente dal server ConCal, se non è stato reso persistente nulla in precedenza. Rappresenta uno dei modi in cui il sistema può specificare le configurazioni di fabbrica. Sono possibili altre configurazioni.
Recupera configurazione
Dopo la registrazione, recupera la configurazione. Poiché la configurazione è stata registrata in precedenza, questa chiamata andrà sicuramente a buon fine.
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")
}
Dove:
La chiamata a
GetConfigRequestha esito positivo, in quanto la configurazione è stata registrata in precedenza dallo stesso bundle. La configurazione consente al bundle di servizi di gestire in modo opaco entrambi i casi di configurazioni di fabbrica e configurazioni sostituite: la logica di business del bundle di servizi rimane invariata.La chiamata
GetConfigRequestrestituisce byte non elaborati. La funzioneget_configprocede con l'analisi per il tipo di configurazione previsto.