Cấu hình và hiệu chuẩn (ConCal)

Dịch vụ Cấu hình và hiệu chỉnh (ConCal) trên nền tảng xe được xác định bằng phần mềm (SDV) cung cấp các khả năng để định cấu hình dịch vụ SDV theo thông số kỹ thuật của xe, quy định của quốc gia và các tính năng mà khách hàng đặt hàng. Dịch vụ này là một khối xây dựng cơ bản của nền tảng SDV, cho phép OEM sử dụng lại cùng một mã dịch vụ giữa nhiều xe bằng cách định cấu hình các xe đó và cho phép định cấu hình lại từ nhiều nguồn (ví dụ: tại nhà máy, trong trung tâm dịch vụ, từ đám mây).

Nền tảng SDV cung cấp các API hướng đến dịch vụ để định cấu hình và hiệu chỉnh các gói dịch vụ trên một chiếc xe cụ thể. Khi sử dụng giao diện này, OEM có thể triển khai logic cấu hình và hiệu chỉnh dành riêng cho OEM.

Dịch vụ Cấu hình và hiệu chỉnh bao gồm các quy trình sau:

  • Cấu hình, bao gồm việc xác định các thuộc tính cơ bản và hành vi của xe và có thể phụ thuộc vào nhiều yếu tố như vị trí của xe, các lựa chọn do người dùng đặt hàng hoặc quy định của quốc gia. Cấu hình này thiết lập cách các thành phần tương tác và chỉ định các chế độ cài đặt phần mềm ảnh hưởng đến chức năng tổng thể của xe, chẳng hạn như các biến thể phần mềm, kết nối mạng và các tham số hoạt động ban đầu.

  • Hiệu chỉnh, giúp tinh chỉnh các tham số hệ thống trong phạm vi đã định cấu hình trước. Ví dụ: hiệu chỉnh sẽ điều chỉnh độ chính xác của cảm biến và bộ truyền động, tối ưu hoá hiệu suất động cơ để kiểm soát khí thải và tinh chỉnh khả năng lái cũng như phản ứng của hệ thống an toàn. Cấu hình thiết lập khung cơ bản cho cách xe hoạt động, trong khi hiệu chỉnh tối ưu hoá hành vi của xe trong khung đó. Cả hai đều rất quan trọng để đảm bảo xe đáp ứng các quy định về khí thải, tối đa hoá hiệu suất, nâng cao độ an toàn và có thể bù đắp cho hao mòn theo thời gian.

Bằng cách cung cấp một API ConCal tiêu chuẩn trên toàn bộ SDV, chúng tôi đơn giản hoá việc triển khai các gói dịch vụ SDV, tránh việc phải triển khai lại các khả năng cấu hình và hiệu chỉnh để chạy trên nhiều xe của nhiều OEM.

Kiến trúc

Mỗi gói dịch vụ có thể sở hữu một hoặc nhiều cấu phần phần mềm cấu hình.

Cấu phần phần mềm cấu hình

Cấu phần phần mềm cấu hình (cấu hình) bao gồm một hoặc nhiều tham số cấu hình và giá trị của các tham số đó. Cấu hình là một thông báo protobuf dành riêng cho dịch vụ mà các trường của thông báo đó có thể bao gồm các thông báo protobuf lồng nhau (cấu trúc), tệp ánh xạ, mảng, int, float, bool, byte hoặc tham số chuỗi.

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

Mã nhận dạng cấu hình

Mỗi cấu hình có một mã nhận dạng riêng. Mã nhận dạng này bao gồm tên thực thể đủ điều kiện của chủ sở hữu gói dịch vụ và tên cấu hình. Tên cấu hình phải dễ đọc, riêng biệt cho mỗi gói dịch vụ, và tuân theo các tiêu chuẩn đặt tên được xác định trong Quy ước đặt tên gói dịch vụ, chẳng hạn như shared, private, diagnostics, và calibration.

Giới hạn:

  • Tên thực thể phải bắt đầu bằng một chữ cái.
  • Tất cả các ký tự phải là chữ và số viết thường hoặc dấu gạch nối.
  • Dấu gạch nối trong tên không được xuất hiện liên tiếp nhiều lần.
  • Tên cấu hình không được kết thúc bằng dấu gạch nối.
  • Tên cấu hình không được dài quá 48 ký tự.
  • Tên cấu hình phải là tên riêng biệt trên cùng một VM cho cùng một gói dịch vụ.

Trước 26Q2, mã cấu hình được xác định như sau:

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

Chủ sở hữu cấu hình khi khởi động chỉ biết giản đồ của cấu hình và các giá trị mặc định của cấu hình đó. Để tuỳ chỉnh hành vi của gói dịch vụ cho một chiếc xe hiện tại, gói dịch vụ sở hữu phải đăng ký cấu hình mặc định cùng với giản đồ của cấu hình đó.

Đăng ký cấu hình mặc định và truy xuất cấu hình tuỳ chỉnh

Hình 1. Đăng ký cấu hình mặc định và truy xuất cấu hình tuỳ chỉnh.

Điều này cho phép OEM triển khai các gói dịch vụ một lần và thực thi các gói dịch vụ đó trên nhiều xe.

Triển khai

ConCal có thể duy trì một hoặc nhiều thực thể máy chủ trên nền tảng SDV. Các gói dịch vụ phải khám phá và sử dụng máy chủ ConCal gần nhất. Ví dụ: nếu ConCal được triển khai một lần cho mỗi ECU, thì gói dịch vụ sẽ có quyền truy cập vào thực thể ConCal đang chạy trên cùng một ECU. Điều này cho phép gói dịch vụ truy xuất cấu hình một cách kịp thời. Nếu thực thể ConCal không có cấu hình được yêu cầu (vì cấu hình đó thuộc phạm vi trách nhiệm của một ConCal khác), thì máy chủ ConCal được liên hệ sẽ yêu cầu cấu hình đó từ thực thể ConCal sở hữu và chuyển hướng cấu hình đó đến gói dịch vụ.

Tuỳ chỉnh cấu hình

Khả năng sử dụng lại cùng một phần mềm với nhiều xe là một trong những ưu điểm chính của SDV. Phần mềm được phát triển một lần rồi được sử dụng lại trên nhiều xe và chúng tôi có thể điều chỉnh hành vi của phần mềm dựa trên thông số kỹ thuật của xe. Đây là mục đích chính của ConCal, giúp tính toán cấu hình dịch vụ dựa trên các thuộc tính của xe với sự trợ giúp của các hoạt động ghi đè cấu hình.

ConfigOverride là một thông báo protobuf mô tả cách điều chỉnh cấu hình cho xe cụ thể. Thông báo này bao gồm một mã ghi đè, được xác định duy nhất bởi thực thể cung cấp mã đó, một mã nhận dạng cấu hình và danh sách ConfigOverrideKeyValuePair. Bạn chỉ có thể cung cấp ConfigOverride trong quá trình cập nhật và chỉ các dịch vụ được phép mới có thể cung cấp (được mô hình hoá bởi OEM). Định nghĩa protobuf cho cả hai cấu trúc được cung cấp bên dưới.

// 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 hỗ trợ các thao tác sau:

  • Gán giá trị mới: Giá trị cuối cùng không được dùng nữa và tham số được gán một giá trị mới, chẳng hạn như gán một giá trị mới cho một trường đơn giản (int, chuỗi, float, bool, byte) hoặc viết lại các trường phức tạp, chẳng hạn như tệp ánh xạ, danh sách, cấu trúc hoặc cấu hình hoàn chỉnh.

  • Xoá hoặc làm sạch giá trị: Thao tác này được cung cấp cho tất cả các loại, bao gồm thông báo, trường lặp lại, tệp ánh xạ, trường đơn và chính cấu hình. Bạn có thể thực hiện thao tác này trên một trường hoặc thông báo hoàn chỉnh, nghĩa là bạn không thể xoá các khoá cụ thể trong tệp ánh xạ và các phần tử riêng lẻ khỏi trường lặp lại.

  • Thêm giá trị mới vào tệp ánh xạ.

  • Viết lại giá trị của khoá hiện có trong tệp ánh xạ (bạn cũng có thể thực hiện thao tác này bằng cách thêm giá trị mới vào tệp ánh xạ, nếu khoá không tồn tại).

Định cấu hình gói dịch vụ bằng ConCal

Chương này mô tả cách phát triển gói dịch vụ truy xuất cấu hình của gói dịch vụ đó trong thời gian chạy. Theo góc độ gói có thể định cấu hình, gói này hoàn toàn không biết liệu cấu hình được truy xuất là chế độ cài đặt mặc định của nhà máy hay là một sửa đổi tiếp theo bằng cách sử dụng các quy trình hiệu chỉnh và ghi đè ConCal.

Bạn có thể tìm thấy mẫu mà tài liệu này dựa trên tại system/software_defined_vehicle/samples/concal/src/concal_client.

Hãy xem phần Phát triển gói dịch vụ để biết thêm thông tin.

Khai báo loại cấu hình do gói dịch vụ sở hữu

Gói dịch vụ sở hữu loại cấu hình mà gói dịch vụ đó truy xuất. Điều này cho phép cập nhật gói có thể định cấu hình một cách độc lập từ dữ liệu cấu hình được duy trì.

  1. Viết tệp protobuf (có đuôi .proto) khai báo loại cấu hình:

    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. Tạo mục tiêu bản dựng tạo thư viện thời gian chạy cho phép truy xuất loại cấu hình. Trong tệp 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",
    }
    

Tạo mã phần mềm trung gian ConCal RPC cho gói của bạn

Thêm một khai báo VSIDL vào gói dịch vụ:

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

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

Điều này khai báo rằng gói là ứng dụng khách của dịch vụ truy xuất và đăng ký cấu hình ConCal. Hãy xem Tổng quan về VSIDL và phần mềm trung gian để biết tổng quan về cách sử dụng VSIDLC để tạo các liên kết ứng dụng khách RPC cho gói.

Khởi chạy phần mềm trung gian để truy xuất cấu hình RPC

Trong thời gian chạy, hãy khởi chạy các thành phần phần mềm trung gian cần thiết cho các lệnh gọi ConCal RPC. Trong ví dụ này, chúng tôi khởi chạy không đồng bộ khi gói được khởi động.

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

Trong trường hợp:

  • Trong on_start, chúng tôi tạo thời gian chạy tokio và tạo một tác vụ tokio. Tác vụ này gọi setup_register_rpc trước khi tiếp tục với logic chính của gói.
  • setup_register_rpc thiết lập liên kết RPC phần mềm trung gian. Xin lưu ý rằng ví dụ này không giả định rằng chức năng ConCal được triển khai bởi một tác nhân: máy chủ có thể chỉ hoạt động sau khi gói được khởi động. Do đó, mã ví dụ sẽ chờ máy chủ RPC được đăng ký bằng cách sử dụng API Khám phá dịch vụ.

Khởi chạy cấu hình

Đăng ký cấu phần phần mềm cấu hình bằng cách cung cấp cho máy chủ ConCal một mã nhận dạng, giản đồ cấu hình và các giá trị cấu hình mặc định của nhà máy.

Nếu bạn gọi quy trình đăng ký lần đầu tiên, giá trị mặc định sẽ được duy trì. Nếu bạn gọi quy trình đăng ký khi khởi động gói tiếp theo, giá trị mặc định được duy trì với các hoạt động ghi đè ConCal đã áp dụng (nếu có) sẽ được truy xuất.

Lệnh gọi cấu hình đăng ký ConCal không bao giờ thất bại, bất kể cấu phần phần mềm đã được đăng ký hay chưa.

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

Trong trường hợp:

  • Tác vụ được tạo trong on_start, sau khi truy xuất liên kết RPC, sẽ tiếp tục với logic kinh doanh, gọi sample_concal_main.
  • sample_concal_main bắt đầu bằng cách đăng ký cấu phần phần mềm cấu hình. Logic có trong register_config.
  • Để đăng ký cấu hình, gói phải chỉ định loại protobuf, mã nhận dạng và giá trị mặc định.
  • config_fd là loại cấu hình. Gói sở hữu loại cấu hình đảm bảo rằng giản đồ mà gói dự kiến luôn được truy xuất, bao gồm cả các bản cập nhật sau APEX.
  • Mã nhận dạng được dùng làm giá trị nhận dạng trong logic duy trì nội bộ của máy chủ ConCal.
  • Giá trị mặc định được tạo trong get_rear_view_camera_factory_config. Giá trị mặc định là giá trị được máy chủ ConCal duy trì, nếu trước đó không có giá trị nào được duy trì. Đây là một trong những cách mà hệ thống có thể chỉ định cấu hình của nhà máy. Bạn có thể thiết lập theo các cách khác.

Truy xuất cấu hình

Sau khi đăng ký, hãy truy xuất cấu hình. Vì cấu hình đã được đăng ký trước đó, nên lệnh gọi này chắc chắn sẽ thành công.

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

Trong trường hợp:

  • Lệnh gọi đến GetConfigRequest chắc chắn sẽ thành công, vì cấu hình đã được đăng ký trước đó bởi cùng một gói. Quy trình thiết lập cho phép gói dịch vụ xử lý một cách không rõ ràng cả hai trường hợp cấu hình của nhà máy và cấu hình bị ghi đè: Logic kinh doanh của gói dịch vụ vẫn không thay đổi.

  • Lệnh gọi GetConfigRequest trả về byte thô. Hàm get_config tiếp tục phân tích cú pháp các byte đó thành loại cấu hình dự kiến.