Der Konfigurations- und Kalibrierungsdienst (Configuration and Calibration Service, ConCal) auf der Software-Defined Vehicle-Plattform (SDV) bietet Funktionen zum Konfigurieren von SDV-Diensten gemäß den Fahrzeugspezifikationen, den Vorschriften des jeweiligen Landes und den vom Kunden bestellten Funktionen. Dieser Dienst ist ein grundlegender Baustein der SDV-Plattform, mit dem OEMs denselben Dienstcode für mehrere Fahrzeuge wiederverwenden können, indem sie sie konfigurieren und die Neukonfiguration aus mehreren Quellen ermöglichen (z. B. in einer Fabrik, in einem Servicecenter oder über die Cloud).
Die SDV-Plattform bietet dienstorientierte APIs für die Konfiguration und Kalibrierung von Dienstpaketen auf einem bestimmten Fahrzeug. Über diese Schnittstelle können OEMs OEM-spezifische Konfigurations- und Kalibrierungslogik implementieren.
Der Konfigurations- und Kalibrierungsdienst umfasst die folgenden Prozesse:
Konfiguration: Hier werden die grundlegenden Eigenschaften und das Verhalten eines Fahrzeugs definiert. Dies kann von mehreren Faktoren abhängen, z. B. vom Standort des Fahrzeugs, von vom Nutzer bestellten Optionen oder von den Vorschriften des jeweiligen Landes. Es wird festgelegt, wie Komponenten interagieren, und Softwareeinstellungen werden vorgegeben, die die Gesamtfunktionalität des Fahrzeugs beeinflussen, z. B. Softwarevarianten, Netzwerkverbindungen und anfängliche Betriebsparameter.
Kalibrierung: Hier werden Systemparameter innerhalb ihrer vorkonfigurierten Bereiche fein abgestimmt. Bei der Kalibrierung wird beispielsweise die Genauigkeit von Sensoren und Aktuatoren angepasst, die Motorleistung für die Emissionskontrolle optimiert und die Reaktion von Fahr- und Sicherheitssystemen verfeinert. Bei der Konfiguration wird der grundlegende Rahmen für die Funktionsweise eines Fahrzeugs festgelegt, während bei der Kalibrierung das Verhalten innerhalb dieses Rahmens optimiert wird. Beide sind entscheidend, damit Fahrzeuge die Emissionsvorschriften erfüllen, die Leistung maximiert, die Sicherheit erhöht und der Verschleiß im Laufe der Zeit kompensiert werden kann.
Durch die Bereitstellung einer standardmäßigen SDV-weiten ConCal API vereinfachen wir die Implementierung von SDV-Dienstpaketen. So müssen Konfigurations- und Kalibrierungsfunktionen nicht neu implementiert werden, um auf verschiedenen Fahrzeugen von verschiedenen OEMs ausgeführt zu werden.
Architektur
Jedes Dienstpaket kann ein oder mehrere Konfigurationsartefakte haben.
Konfigurationsartefakte
Ein Konfigurationsartefakt (Konfiguration) besteht aus einem oder mehreren Konfigurationsparametern und ihren Werten. Die Konfiguration ist eine dienstspezifische Protobuf-Nachricht, deren Felder verschachtelte Protobuf-Nachrichten (Struktur), Zuordnungen, Arrays, Ganzzahlen, Gleitkommazahlen, boolesche Werte, Byte- oder Stringparameter enthalten können.
// 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;
}
Konfigurations-ID
Jede Konfiguration hat eine eindeutige ID. Diese ID besteht aus dem vollständig qualifizierten Instanznamen des Dienstpaketbesitzers und einem Konfigurationsnamen. Ein Konfigurationsname muss für Menschen lesbar und pro Dienstpaket eindeutig sein,
und den in
den Namenskonventionen für Dienstpakete definierten Namensstandards entsprechen,
z. B. shared, private, diagnostics, und calibration.
Einschränkungen:
- Der Instanzname muss mit einem Buchstaben beginnen.
- Alle Zeichen müssen alphanumerische Kleinbuchstaben oder ein Bindestrich sein.
- Bindestriche im Namen dürfen nicht mehr als einmal hintereinander vorkommen.
- Der Konfigurationsname darf nicht mit einem Bindestrich enden.
- Der Konfigurationsname darf nicht länger als 48 Zeichen sein.
- Konfigurationsnamen müssen auf derselben VM für dasselbe Dienstpaket eindeutig sein.
Vor dem 2. Quartal 2026 ist die Konfigurations-ID so definiert:
// 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;
}
Der Konfigurationsinhaber kennt beim Start nur das Schema seiner Konfiguration und die Standardwerte. Um das Verhalten des Dienstpakets an ein aktuelles Fahrzeug anzupassen, muss das zugehörige Dienstpaket seine Standardkonfiguration zusammen mit dem Schema registrieren.
Abbildung 1 : Registrierung der Standardkonfiguration und Abruf der benutzerdefinierten Konfiguration.
So können OEMs Dienstpakete einmal implementieren und auf mehreren Fahrzeugen ausführen.
Bereitstellung
ConCal kann eine oder mehrere Serverinstanzen auf der SDV-Plattform verwalten. Dienstpakete sollten den nächstgelegenen ConCal-Server ermitteln und verwenden. Wenn ConCal beispielsweise einmal pro Steuergerät bereitgestellt wird, sollte ein Dienstpaket Zugriff auf die ConCal-Instanz haben, die auf demselben Steuergerät ausgeführt wird. So kann das Dienstpaket die Konfiguration rechtzeitig abrufen. Wenn die ConCal-Instanz keine angeforderte Konfiguration hat (da sie zum Zuständigkeitsbereich eines anderen ConCal gehört), fordert der kontaktierte ConCal-Server sie von der zugehörigen ConCal-Instanz an und leitet sie an das Dienstpaket weiter.
Konfiguration anpassen
Die Möglichkeit, dieselbe Software mit verschiedenen Fahrzeugen wiederzuverwenden, ist einer der Hauptvorteile von SDV. Die Software wird einmal entwickelt und dann auf mehreren Fahrzeugen wiederverwendet. Das Verhalten der Software kann an die Besonderheiten des Fahrzeugs angepasst werden. Das ist der Hauptzweck von ConCal. Hier wird die Dienstkonfiguration anhand der Fahrzeugeigenschaften mit Konfigurationsüberschreibungen berechnet.
ConfigOverride ist eine Protobuf-Nachricht, die beschreibt, wie die Konfiguration an das jeweilige Fahrzeug angepasst wird. Sie besteht aus einer Überschreibungs-ID, die eindeutig von der Entität definiert wird, die sie bereitstellt, einer Konfigurations-ID und einer Liste von ConfigOverrideKeyValuePair. ConfigOverride kann nur während des Aktualisierungsprozesses und nur von den zulässigen Diensten bereitgestellt werden, was vom OEM modelliert wird. Die Protobuf-Definitionen für beide Strukturen finden Sie unten.
// 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 unterstützt die folgenden Vorgänge:
Neuen Wert zuweisen: Der letzte Wert wird verworfen und dem Parameter wird ein neuer Wert zugewiesen, z. B. einem einfachen Feld (Ganzzahl, String, Gleitkommazahl, boolescher Wert, Byte) oder komplexe Felder wie Zuordnungen, Listen, Strukturen oder eine vollständige Konfiguration werden neu geschrieben.
Wert entfernen oder löschen: Dieser Vorgang ist für alle Typen verfügbar, einschließlich Nachrichten, wiederholter Felder, Zuordnungen, einzelner Felder und der Konfiguration selbst. Dieser Vorgang kann für ein vollständiges Feld oder eine vollständige Nachricht ausgeführt werden. Das Entfernen bestimmter Schlüssel in einer Zuordnung und einzelner Elemente aus einem wiederholten Feld wird nicht unterstützt.
Einen neuen Wert zu einer Zuordnung hinzufügen.
Den Wert eines vorhandenen Schlüssels in einer Zuordnung neu schreiben. Dieser Vorgang kann auch als Hinzufügen eines neuen Werts zu einer Zuordnung ausgeführt werden, wenn der Schlüssel nicht vorhanden ist.
Dienstpaket mit ConCal konfigurieren
In diesem Kapitel wird beschrieben, wie Sie ein Dienstpaket entwickeln, das seine Konfiguration zur Laufzeit abruft. Aus Sicht eines konfigurierbaren Pakets ist es völlig undurchsichtig, ob es sich bei der abgerufenen Konfiguration um eine Standardeinstellung ab Werk oder um eine spätere Änderung mithilfe von ConCal-Überschreibungs- und Kalibrierungsprozessen handelt.
Das Beispiel, auf dem die Dokumentation basiert, finden Sie unter system/software_defined_vehicle/samples/concal/src/concal_client.
Weitere Informationen finden Sie unter Dienstpaketentwicklung.
Typ der Konfiguration deklarieren, die dem Dienstpaket gehört
Das Dienstpaket ist der Inhaber des Typs der Konfiguration, die es abruft. So kann ein konfigurierbares Paket unabhängig von den gespeicherten Konfigurationsdaten aktualisiert werden.
Schreiben Sie eine Protobuf-Datei (mit der Erweiterung
.proto), in der der Konfigurationstyp deklariert wird: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; }Erstellen Sie ein Build-Ziel, das eine Laufzeitbibliothek generiert, mit der der Konfigurationstyp abgerufen werden kann. In einer
Android.bp-Datei: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-Middleware-Code für Ihr Paket generieren
Fügen Sie dem Dienstpaket eine VSIDL-Deklaration hinzu:
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
Dadurch wird deklariert, dass das Paket ein Client des ConCal-Dienstes zur Registrierung und zum Abruf von Konfigurationen ist. Eine Übersicht über die Verwendung von VSIDLC zum Generieren von RPC-Clientbindungen für das Paket finden Sie unter VSIDL und Middleware – Übersicht für.
Middleware für den RPC-Konfigurationsabruf initialisieren
Initialisieren Sie zur Laufzeit die Middleware-Komponenten, die für ConCal-RPC-Aufrufe erforderlich sind. In diesem Beispiel wird die asynchrone Initialisierung beim Starten des Pakets durchgeführt.
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")
}
Wobei:
- In
on_starterstellen wir eine Tokio-Laufzeit und starten eine Tokio-Aufgabe. Die Aufgabe ruftsetup_register_rpcauf, bevor mit der Hauptlogik des Pakets fortgefahren wird. setup_register_rpcrichtet die Middleware-RPC-Bindung ein. Im Beispiel wird nicht davon ausgegangen, dass die ConCal-Funktionalität von einem Agent implementiert wird. Der Server ist möglicherweise erst verfügbar, nachdem das Paket gestartet wurde. Daher wartet der Beispielcode darauf, dass der RPC-Server mit der Service Discovery API registriert wird.
Konfiguration initialisieren
Registrieren Sie das Konfigurationsartefakt, indem Sie dem ConCal-Server eine ID, das Konfigurationsschema und die Standardkonfigurationswerte ab Werk zur Verfügung stellen.
Wenn die Registrierung zum ersten Mal aufgerufen wird, wird der Standardwert gespeichert. Wenn die Registrierung bei nachfolgenden Paketstarts aufgerufen wird, wird der gespeicherte Standardwert mit angewendeten ConCal-Überschreibungen (falls vorhanden) abgerufen.
Der ConCal-Registrierungsaufruf schlägt nie fehl, unabhängig davon, ob das Artefakt bereits registriert ist.
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",
);
}
Wobei:
- Die in
on_startgestartete Aufgabe fährt nach dem Abrufen der RPC-Bindung mit der Geschäftslogik fort und ruftsample_concal_mainauf. sample_concal_mainbeginnt mit der Registrierung eines Konfigurationsartefakts. Die Logik ist inregister_configenthalten.- Um eine Konfiguration zu registrieren, muss ein Paket seinen Protobuf-Typ, eine ID und einen Standardwert angeben.
config_fdist der Konfigurationstyp. Das Paket, das den Konfigurationstyp besitzt, sorgt dafür, dass immer das vom Paket erwartete Schema abgerufen wird, einschließlich nach APEX-Updates.- Die ID wird als Kennung in der internen Persistenzlogik des ConCal-Servers verwendet.
- Der Standardwert wird in
get_rear_view_camera_factory_configerstellt. Der Standardwert ist der Wert, der vom ConCal-Server gespeichert wird, wenn zuvor nichts gespeichert wurde. Dies ist eine der Möglichkeiten, mit denen das System Konfigurationen ab Werk angeben kann. Andere Setups sind möglich.
Konfiguration abrufen
Rufen Sie nach der Registrierung die Konfiguration ab. Da die Konfiguration zuvor registriert wurde, ist garantiert, dass dieser Aufruf erfolgreich ist.
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")
}
Wobei:
Der Aufruf von
GetConfigRequestist garantiert erfolgreich, da die Konfiguration zuvor vom selben Paket registriert wurde. Das Setup ermöglicht es dem Dienstpaket, sowohl Konfigurationen ab Werk als auch überschriebene Konfigurationen undurchsichtig zu verarbeiten. Die Geschäftslogik des Dienstpakets bleibt unverändert.Der Aufruf
GetConfigRequestgibt Rohbytes zurück. Die Funktionget_configanalysiert sie dann in den erwarteten Konfigurationstyp.