Le service de configuration et de calibration (ConCal) sur la plate-forme de véhicule défini par logiciel (SDV) permet de configurer les services SDV en fonction des spécifications du véhicule, des réglementations du pays et des fonctionnalités commandées par le client. Ce service est un élément de base de la plate-forme SDV. Il permet aux OEM de réutiliser le même code de service sur plusieurs véhicules en les configurant et en permettant la reconfiguration à partir de plusieurs sources (par exemple, dans une usine, dans un centre de réparation ou depuis le cloud).
La plate-forme SDV fournit des API orientées service pour la configuration et la calibration des packs de services sur un véhicule spécifique. Cette interface permet aux OEM d'implémenter une logique de configuration et de calibration spécifique aux OEM.
Le service de configuration et de calibration comprend les processus suivants :
La configuration, qui consiste à définir les propriétés et le comportement fondamentaux d'un véhicule, et qui peut dépendre de plusieurs facteurs tels que l'emplacement du véhicule, les options commandées par l'utilisateur ou les réglementations du pays. Il établit la manière dont les composants interagissent et définit les paramètres logiciels qui influencent la fonctionnalité globale du véhicule, tels que les variantes logicielles, les connexions réseau et les paramètres opérationnels initiaux.
Calibration, qui ajuste précisément les paramètres du système dans leurs plages préconfigurées. Par exemple, la calibration ajuste la précision des capteurs et des actionneurs, optimise les performances du moteur pour le contrôle des émissions, et affine la réponse des systèmes de conduite et de sécurité. La configuration définit le cadre de base du fonctionnement d'un véhicule, tandis que la calibration optimise son comportement dans ce cadre. Ces deux éléments sont essentiels pour garantir que les véhicules respectent les réglementations sur les émissions, maximisent leurs performances, améliorent la sécurité et peuvent compenser l'usure au fil du temps.
En fournissant une API ConCal standard pour l'ensemble des SDV, nous simplifions l'implémentation des bundles de services SDV, ce qui évite de devoir réimplémenter les fonctionnalités de configuration et de calibration pour qu'elles s'exécutent sur différents véhicules de différents OEM.
Architecture
Chaque bundle de services peut posséder un ou plusieurs artefacts de configuration.
Artefacts de configuration
Un artefact de configuration (config) se compose d'un ou de plusieurs paramètres de configuration et de leurs valeurs. La configuration est un message protobuf spécifique au service dont les champs peuvent inclure des messages protobuf imbriqués (struct), des cartes, des tableaux, des paramètres 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;
}
Identifiant de configuration
Chaque configuration possède un identifiant unique. Cet identifiant se compose du nom d'instance complet du propriétaire du bundle de services et d'un nom de configuration. Un nom de configuration doit être lisible par un humain, unique par bundle de services et respecter les normes de dénomination définies dans Conventions de dénomination des bundles de services, telles que shared, private, diagnostics et calibration.
Restrictions :
- Le nom de l'instance doit commencer par une lettre.
- Tous les caractères doivent être alphanumériques en minuscules ou un tiret.
- Les tirets ne doivent pas apparaître plus d'une fois de suite dans le nom.
- Le nom de la configuration ne doit pas se terminer par un trait d'union.
- Le nom de la configuration ne doit pas dépasser 48 caractères.
- Les noms de configuration doivent être uniques sur la même VM pour le même bundle de services.
Avant le T2 2026, l'ID de configuration est défini comme suit :
// 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;
}
Au démarrage, le propriétaire de la configuration ne connaît que le schéma de sa configuration et ses valeurs par défaut. Pour personnaliser le comportement d'un bundle de services pour un véhicule actuel, le bundle de services propriétaire doit enregistrer sa configuration par défaut avec son schéma.
Figure 1. Enregistrement de la configuration par défaut et récupération de la configuration personnalisée.
Cela permet aux OEM d'implémenter des packs de services une seule fois et de les exécuter sur plusieurs véhicules.
Déploiement
ConCal peut gérer une ou plusieurs instances de serveur sur la plate-forme SDV. Les bundles de services doivent découvrir et utiliser le serveur ConCal le plus proche. Par exemple, si ConCal est déployé à raison d'un par ECU, un bundle de services doit avoir accès à l'instance ConCal s'exécutant sur le même ECU. Cela permet au bundle de services de récupérer la configuration en temps voulu. Si l'instance ConCal ne dispose pas de la configuration demandée (car elle relève de la responsabilité d'un autre ConCal), le serveur ConCal contacté la demande à l'instance ConCal propriétaire et la redirige vers le bundle de services.
Personnalisation de la configuration
La possibilité de réutiliser le même logiciel avec différents véhicules est l'un des principaux avantages des véhicules définis par logiciel. Le logiciel est développé une seule fois, puis réutilisé sur plusieurs véhicules. Nous pouvons ajuster son comportement en fonction des spécificités du véhicule. C'est l'objectif principal de ConCal, qui calcule la configuration du service en fonction des propriétés du véhicule à l'aide des remplacements de configuration.
ConfigOverride est un message protobuf qui décrit comment ajuster la configuration au véhicule spécifique. Il se compose d'un ID de forçage, qui est défini de manière unique par l'entité qui le fournit, d'un identifiant de configuration et d'une liste de ConfigOverrideKeyValuePair. ConfigOverride ne peut être fourni que pendant le processus de mise à jour et uniquement par les services autorisés, qui sont modélisés par l'OEM. Les définitions protobuf pour les deux structures sont fournies ci-dessous.
// 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 est compatible avec les opérations suivantes :
Attribuer une nouvelle valeur : la dernière valeur est obsolète et une nouvelle valeur est attribuée au paramètre, par exemple une nouvelle valeur à un champ simple (int, string, float, bool, bytes) ou en réécrivant des champs complexes, tels que des cartes, des listes, des structs ou une configuration complète.
Supprimer ou nettoyer une valeur : cette opération est disponible pour tous les types, y compris les messages, les champs répétés, les cartes, les champs singuliers et la configuration elle-même. Cette opération peut être effectuée sur un champ ou un message complets, ce qui signifie que la suppression de clés spécifiques dans une carte et d'éléments individuels d'un champ répété n'est pas prise en charge.
Ajoutez une valeur à une carte.
Réécrivez la valeur d'une clé existante dans un mappage (cette opération peut également être effectuée en ajoutant une nouvelle valeur à un mappage, si la clé n'existe pas).
Configurer un forfait de services à l'aide de ConCal
Ce chapitre explique comment développer un bundle de services qui récupère sa configuration au moment de l'exécution. Du point de vue d'un bundle configurable, il est impossible de savoir si la configuration récupérée est un paramètre d'usine par défaut ou une modification ultérieure à l'aide des processus de remplacement et d'étalonnage de ConCal.
Vous trouverez l'exemple sur lequel la documentation est basée à l'adresse system/software_defined_vehicle/samples/concal/src/concal_client.
Pour en savoir plus, consultez Développement de bundles de services.
Déclarer le type de configuration détenu par le bundle de services
Le bundle de services possède le type de configuration qu'il récupère. Cela permet de mettre à jour un bundle configurable indépendamment des données de configuration persistantes.
Écrivez un fichier protobuf (avec une extension
.proto) déclarant le type de configuration :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; }Créez une cible de compilation qui génère une bibliothèque d'exécution permettant la récupération du type de configuration. Dans un fichier
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", }
Générer le code middleware RPC ConCal pour votre bundle
Ajoutez une déclaration VSIDL au pack de services :
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
Cela déclare que le bundle est un client du service d'enregistrement et de récupération de la configuration ConCal. Consultez Présentation de VSIDL et du middleware pour obtenir une vue d'ensemble de l'utilisation de VSIDLC pour générer des liaisons de client RPC pour le bundle.
Initialiser le middleware pour la récupération de la configuration RPC
Au moment de l'exécution, initialisez les composants middleware requis pour les appels RPC ConCal. Dans cet exemple, nous initialisons de manière asynchrone lorsque le bundle est démarré.
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")
}
Où :
- Dans
on_start, nous créons un environnement d'exécution tokio et générons une tâche tokio. La tâche appellesetup_register_rpcavant de poursuivre la logique principale du bundle. setup_register_rpcconfigure la liaison RPC du middleware. Notez que l'exemple ne suppose pas que la fonctionnalité ConCal est implémentée par un agent : le serveur peut ne devenir disponible qu'après le démarrage du bundle. Par conséquent, l'exemple de code attend que le serveur RPC soit enregistré à l'aide de l'API Service Discovery.
Initialiser la configuration
Enregistrez l'artefact de configuration en fournissant au serveur ConCal un ID, le schéma de configuration et les valeurs de configuration par défaut et d'usine.
Si l'enregistrement est appelé pour la première fois, la valeur par défaut est conservée. Si l'enregistrement est appelé lors des démarrages de bundle suivants, la valeur par défaut persistante avec les remplacements ConCal appliqués (le cas échéant) sera récupérée.
L'appel de configuration du registre ConCal n'échoue jamais, que l'artefact soit déjà enregistré ou non.
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",
);
}
Où :
- La tâche générée dans
on_start, après avoir récupéré la liaison RPC, se poursuit avec la logique métier en appelantsample_concal_main. sample_concal_maincommence par enregistrer un artefact de configuration. La logique est contenue dansregister_config.- Pour enregistrer une configuration, un bundle doit spécifier son type protobuf, un ID et une valeur par défaut.
config_fdcorrespond au type de configuration. Le bundle propriétaire du type de configuration garantit que le schéma attendu par le bundle est toujours récupéré, y compris après les mises à jour APEX.- L'ID est utilisé comme identifiant dans la logique de persistance interne du serveur ConCal.
- La valeur par défaut est construite dans
get_rear_view_camera_factory_config. La valeur par défaut est celle conservée par le serveur ConCal, si aucune valeur n'a été conservée auparavant. Il s'agit de l'une des méthodes permettant au système de spécifier les configurations d'usine. D'autres configurations sont possibles.
Récupérer la configuration
Après l'enregistrement, récupérez la configuration. Comme la configuration a déjà été enregistrée, cet appel est garanti de réussir.
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")
}
Où :
L'appel à
GetConfigRequestest garanti de réussir, car la configuration a été enregistrée auparavant par le même bundle. La configuration permet au bundle de services de gérer de manière opaque les configurations d'usine et les configurations remplacées : la logique métier du bundle de services reste inchangée.L'appel
GetConfigRequestrenvoie des octets bruts. La fonctionget_configprocède à leur analyse pour les convertir au type de configuration attendu.