Konfigurasi dan kalibrasi (ConCal)

Layanan Konfigurasi dan Kalibrasi (ConCal) di platform kendaraan software-defined (SDV) menyediakan kemampuan untuk mengonfigurasi layanan SDV sesuai dengan spesifikasi kendaraan, peraturan negara, dan fitur yang dipesan pelanggan. Layanan ini adalah blok penyusun fundamental platform SDV, yang memungkinkan OEM menggunakan kembali kode layanan yang sama di beberapa kendaraan dengan mengonfigurasi dan mengaktifkan konfigurasi ulang dari berbagai sumber (misalnya, di pabrik, di pusat servis, dari cloud).

Platform SDV menyediakan API yang menghadap layanan untuk konfigurasi dan kalibrasi paket layanan pada kendaraan tertentu. Dengan menggunakan antarmuka ini, OEM dapat menerapkan konfigurasi dan logika kalibrasi khusus OEM.

Layanan Konfigurasi dan Kalibrasi mencakup proses berikut:

  • Konfigurasi, yang melibatkan penentuan properti dan perilaku mendasar kendaraan dan mungkin bergantung pada beberapa faktor seperti lokasi kendaraan, opsi yang dipesan pengguna, atau peraturan negara. Hal ini menetapkan cara komponen berinteraksi dan menentukan setelan software yang memengaruhi fungsi keseluruhan kendaraan, seperti varian software, koneksi jaringan, dan parameter operasional awal.

  • Kalibrasi, yang menyetel parameter sistem dalam rentang yang telah dikonfigurasi sebelumnya. Misalnya, kalibrasi menyesuaikan akurasi sensor dan aktuator, mengoptimalkan performa mesin untuk mengontrol emisi, serta meningkatkan kualitas berkendara dan respons sistem keselamatan. Konfigurasi menetapkan framework dasar untuk cara kerja kendaraan, sementara kalibrasi mengoptimalkan perilakunya dalam framework tersebut. Keduanya sangat penting untuk memastikan kendaraan memenuhi peraturan emisi, memaksimalkan performa, meningkatkan keselamatan, dan dapat mengompensasi keausan seiring waktu.

Dengan menyediakan ConCal API standar di seluruh SDV, kami menyederhanakan penerapan paket layanan SDV, sehingga tidak perlu menerapkan ulang kemampuan konfigurasi dan kalibrasi untuk dijalankan di berbagai kendaraan dari berbagai OEM.

Arsitektur

Setiap paket layanan dapat memiliki satu atau beberapa artefak konfigurasi.

Artefak konfigurasi

Artefak konfigurasi (config) terdiri dari satu atau beberapa parameter konfigurasi dan nilainya. Konfigurasi adalah pesan protobuf khusus layanan yang kolomnya dapat mencakup pesan protobuf bertingkat (struct), peta, array, int, float, bool, byte, atau parameter 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;
}

ID konfigurasi

Setiap konfigurasi memiliki ID unik. ID ini terdiri dari nama instance yang sepenuhnya memenuhi syarat dari pemilik paket layanan dan nama konfigurasi. Nama konfigurasi harus mudah dibaca, unik per paket layanan, dan mengikuti standar penamaan yang ditentukan dalam Konvensi penamaan paket layanan, seperti shared, private, diagnostics, dan calibration.

Pembatasan:

  • Nama instance harus diawali dengan huruf.
  • Semua karakter harus berupa alfanumerik huruf kecil atau tanda hubung.
  • Tanda hubung dalam nama tidak boleh muncul lebih dari sekali secara berurutan.
  • Nama konfigurasi tidak boleh diakhiri dengan tanda hubung.
  • Nama konfigurasi tidak boleh lebih dari 48 karakter.
  • Nama konfigurasi harus unik di VM yang sama untuk paket layanan yang sama.

Sebelum 26Q2, ID konfigurasi ditentukan sebagai berikut:

// 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;
}

Pemilik konfigurasi saat booting hanya mengetahui skema konfigurasinya dan nilai defaultnya. Untuk menyesuaikan perilaku paket layanan ke kendaraan saat ini, paket layanan pemilik harus mendaftarkan konfigurasi defaultnya beserta skemanya.

Pendaftaran konfigurasi default dan pengambilan konfigurasi yang disesuaikan

Gambar 1. Pendaftaran konfigurasi default dan pengambilan konfigurasi yang disesuaikan.

Hal ini memungkinkan OEM menerapkan paket layanan sekali dan menjalankannya di beberapa kendaraan.

Deployment

ConCal dapat memelihara satu atau beberapa instance server di platform SDV. Paket layanan harus menemukan dan menggunakan server ConCal terdekat. Misalnya, jika ConCal di-deploy satu per ECU, paket layanan harus mendapatkan akses ke instance ConCal yang berjalan di ECU yang sama. Hal ini memungkinkan paket layanan mengambil konfigurasi secara tepat waktu. Jika instance ConCal tidak memiliki konfigurasi yang diminta (karena termasuk dalam area tanggung jawab ConCal lain), server ConCal yang dihubungi akan memintanya dari instance ConCal pemilik dan mengalihkannya ke paket layanan.

Penyesuaian konfigurasi

Kemampuan untuk menggunakan kembali software yang sama dengan kendaraan yang berbeda adalah salah satu keunggulan utama SDV. Software dikembangkan satu kali, lalu digunakan kembali di beberapa kendaraan, dan kita dapat menyesuaikan perilaku software berdasarkan spesifikasi kendaraan. Ini adalah tujuan utama ConCal, yang menghitung konfigurasi layanan berdasarkan properti kendaraan dengan bantuan penggantian konfigurasi.

ConfigOverride adalah pesan protobuf yang menjelaskan cara menyesuaikan konfigurasi ke kendaraan tertentu. Objek ini terdiri dari ID penggantian, yang didefinisikan secara unik oleh entitas yang menyediakannya, ID konfigurasi, dan daftar ConfigOverrideKeyValuePair. ConfigOverride hanya dapat diberikan selama proses update dan hanya oleh layanan yang diizinkan, yang dimodelkan oleh OEM. Definisi protobuf untuk kedua struktur tersebut disediakan di bawah.

// 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 mendukung operasi berikut:

  • Menetapkan nilai baru: Nilai terakhir tidak digunakan lagi dan parameter diberi nilai baru, seperti menetapkan nilai baru ke kolom sederhana (int, string, float, bool, byte) atau menulis ulang kolom kompleks, seperti peta, daftar, struct, atau konfigurasi lengkap.

  • Menghapus atau membersihkan nilai: Operasi ini disediakan untuk semua jenis, termasuk pesan, kolom berulang, peta, kolom tunggal, dan konfigurasi itu sendiri. Operasi ini dapat dilakukan pada kolom atau pesan lengkap, yang berarti penghapusan kunci tertentu dalam peta dan elemen individual dari kolom berulang tidak didukung.

  • Menambahkan nilai baru ke peta.

  • Menulis ulang nilai kunci yang ada dalam peta (operasi ini juga dapat dilakukan sebagai penambahan nilai baru ke peta, jika kunci tidak ada).

Mengonfigurasi paket layanan menggunakan ConCal

Bab ini menjelaskan cara mengembangkan paket layanan yang mengambil konfigurasinya saat runtime. Dari perspektif paket yang dapat dikonfigurasi, tidak jelas apakah konfigurasi yang diambil adalah setelan pabrik default, atau modifikasi berikutnya menggunakan proses penggantian dan kalibrasi ConCal.

Anda dapat menemukan sampel yang menjadi dasar dokumentasi di system/software_defined_vehicle/samples/concal/src/concal_client.

Lihat Pengembangan paket layanan untuk mengetahui detail selengkapnya.

Mendeklarasikan jenis konfigurasi yang dimiliki oleh paket layanan

Paket layanan memiliki jenis konfigurasi yang diambilnya. Hal ini memungkinkan paket yang dapat dikonfigurasi diperbarui secara terpisah dari data konfigurasi yang dipertahankan.

  1. Tulis file protobuf (dengan ekstensi .proto) yang mendeklarasikan jenis konfigurasi:

    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;
    }
    
  2. Buat target build yang menghasilkan library runtime yang memungkinkan pengambilan jenis konfigurasi. Dalam 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",
    }
    

Membuat kode middleware RPC ConCal untuk paket Anda

Tambahkan deklarasi VSIDL ke paket layanan:

package: "com.sdv.oem.sample.concal"

service_bundle {
  name: "SampleOemConCalClientServiceBundle"
  client {
    service: "com.sdv.google.concal.ConCalRegistrationService"
  }
}

Ini menyatakan bahwa paket adalah klien dari layanan pendaftaran dan pengambilan konfigurasi ConCal. Lihat Ringkasan VSIDL dan middleware untuk mengetahui ringkasan penggunaan VSIDLC dalam membuat binding klien RPC untuk bundle.

Lakukan inisialisasi middleware untuk pengambilan konfigurasi RPC

Saat runtime, inisialisasi komponen middleware yang diperlukan untuk panggilan RPC ConCal. Dalam contoh ini, kita melakukan inisialisasi secara asinkron saat paket dimulai.

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")
}

Dengan:

  • Di on_start, kita membuat runtime tokio dan memunculkan tugas tokio. Tugas memanggil setup_register_rpc, sebelum melanjutkan dengan logika utama bundle.
  • setup_register_rpc menyiapkan binding RPC middleware. Perhatikan bahwa contoh tidak mengasumsikan bahwa fungsi ConCal diimplementasikan oleh agen: server mungkin hanya tersedia setelah paket dimulai. Oleh karena itu, contoh kode menunggu server RPC didaftarkan menggunakan Service Discovery API.

Lakukan inisialisasi konfigurasi

Daftarkan artefak konfigurasi dengan memberikan ID, skema konfigurasi, dan nilai konfigurasi default pabrik ke server ConCal.

Jika pendaftaran dipanggil untuk pertama kalinya, nilai default akan dipertahankan. Jika pendaftaran dipanggil pada permulaan paket berikutnya, nilai default yang dipertahankan dengan penggantian ConCal yang diterapkan (jika ada), akan diambil.

Panggilan konfigurasi pendaftaran ConCal tidak pernah gagal, terlepas dari apakah artefak sudah terdaftar atau belum.

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(&registration_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",
        );
}

Dengan:

  • Tugas yang dibuat di on_start, setelah mengambil binding RPC, akan melanjutkan dengan logika bisnis, memanggil sample_concal_main.
  • sample_concal_main dimulai dengan mendaftarkan artefak konfigurasi. Logika terdapat di register_config.
  • Untuk mendaftarkan konfigurasi, paket harus menentukan jenis protobuf, ID, dan nilai default-nya.
  • config_fd adalah jenis konfigurasi. Bundle yang memiliki jenis konfigurasi memastikan bahwa skema yang diharapkan oleh bundle selalu diambil, termasuk setelah update APEX.
  • ID digunakan sebagai ID dalam logika persistensi internal server ConCal.
  • Nilai default dibuat di get_rear_view_camera_factory_config. Nilai default adalah nilai yang dipertahankan oleh server ConCal, jika tidak ada yang dipertahankan sebelumnya. Ini adalah salah satu cara sistem dapat menentukan konfigurasi pabrik. Penyiapan lain juga dapat dilakukan.

Mengambil konfigurasi

Setelah pendaftaran, ambil konfigurasi. Karena konfigurasi telah didaftarkan sebelumnya, panggilan ini dijamin akan berhasil.

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(&registration_client, &config_id, &config).await;
    let config = get_config(&registration_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")
}

Dengan:

  • Panggilan ke GetConfigRequest dijamin berhasil, karena konfigurasi telah didaftarkan sebelumnya, oleh bundle yang sama. Penyiapan memungkinkan paket layanan menangani secara transparan kedua kasus konfigurasi pabrik dan konfigurasi yang diganti: Logika bisnis paket layanan tetap tidak berubah.

  • Panggilan GetConfigRequest menampilkan byte mentah. Fungsi get_config melanjutkan dengan menguraikannya ke jenis konfigurasi yang diharapkan.