Настройка и калибровка (ConCal)

Сервис конфигурации и калибровки (ConCal) на платформе программно-определяемого транспортного средства (SDV) предоставляет возможности для настройки сервисов SDV в соответствии со спецификациями транспортного средства, национальными правилами и функциями, заказанными заказчиком. Этот сервис является фундаментальным элементом платформы SDV, позволяя производителям оригинального оборудования (OEM) повторно использовать один и тот же код сервиса для нескольких транспортных средств путем их настройки и обеспечения возможности переконфигурации из различных источников (например, на заводе, в сервисном центре, из облака).

Платформа SDV предоставляет API-интерфейсы для настройки и калибровки пакетов услуг для конкретного транспортного средства. Используя этот интерфейс, производители могут внедрять специфичную для каждого производителя логику настройки и калибровки.

Услуга по настройке и калибровке включает в себя следующие процессы:

  • Конфигурация , которая включает в себя определение основных свойств и поведения транспортного средства и может зависеть от множества факторов, таких как местоположение транспортного средства, выбранные пользователем опции или правила страны. Она определяет, как компоненты взаимодействуют, и задает параметры программного обеспечения, влияющие на общую функциональность транспортного средства, такие как варианты программного обеспечения, сетевые подключения и начальные рабочие параметры.

  • Калибровка — это точная настройка параметров системы в пределах заданных диапазонов. Например, калибровка регулирует точность датчиков и исполнительных механизмов, оптимизирует работу двигателя для контроля выбросов, а также улучшает управляемость и реакцию систем безопасности. Конфигурация задает базовые параметры функционирования автомобиля, а калибровка оптимизирует его поведение в рамках этих параметров. Оба процесса имеют решающее значение для обеспечения соответствия транспортных средств нормам выбросов, максимальной производительности, повышения безопасности и компенсации износа с течением времени.

Предоставляя стандартный API ConCal для всей системы SDV, мы упрощаем внедрение пакетов услуг SDV, избегая необходимости повторной реализации возможностей конфигурации и калибровки для работы на различных автомобилях от разных производителей.

Архитектура

Каждый пакет сервисов может содержать один или несколько конфигурационных артефактов.

Артефакты конфигурации

Конфигурационный артефакт (config) состоит из одного или нескольких параметров конфигурации и их значений. Конфигурация представляет собой специфичное для сервиса сообщение protobuf, поля которого могут включать вложенные сообщения protobuf (структуры), карты, массивы, параметры типа int, float, bool, bytes или 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;
}

Идентификатор конфигурации

Конфигурация имеет уникальный идентификатор, состоящий из полного имени экземпляра пакета служб (его владельца) и имени конфигурации. Имя конфигурации должно быть удобочитаемым, уникальным для каждого пакета служб и соответствовать стандартам именования, определенным в «Соглашениях об именовании» , например, shared , private , diagnostics и calibration .

Ограничения:

  • Имя экземпляра должно начинаться с буквы.
  • Все символы должны быть строчными буквенно-цифровыми или содержать дефис.
  • Дефисы в имени не должны встречаться подряд более одного раза.
  • Название конфигурации не должно заканчиваться дефисом.
  • Название конфигурации не должно превышать 48 символов.
  • Названия конфигурационных файлов должны быть уникальными на одной и той же виртуальной машине для одного и того же пакета служб.

До 26Q2 идентификатор конфигурации определялся следующим образом:

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

Владелец конфигурации при загрузке знает только схему своей конфигурации и её значения по умолчанию. Для настройки поведения пакета служб под конкретный автомобиль, владеющий пакет служб должен зарегистрировать свою конфигурацию по умолчанию вместе со своей схемой.

Регистрация конфигурации по умолчанию и получение пользовательской конфигурации. конфигурация

Рисунок 1. Регистрация конфигурации по умолчанию и получение пользовательской конфигурации.

Это позволяет производителям оригинального оборудования внедрять пакеты услуг один раз и применять их к нескольким автомобилям.

Развертывание

ConCal может поддерживать один или несколько экземпляров сервера на платформе SDV. Пакеты услуг должны обнаруживать и использовать ближайший сервер ConCal. Например, если ConCal развернут по одному на каждый блок управления (ECU), пакет услуг должен получить доступ к экземпляру ConCal, работающему на том же блоке управления. Это позволяет пакету услуг своевременно получать конфигурацию. Если у экземпляра ConCal нет запрошенной конфигурации (поскольку он относится к зоне ответственности другого ConCal), сервер ConCal, с которым осуществляется связь, запрашивает ее у владеющего экземпляра ConCal и перенаправляет запрос пакету услуг.

Настройка конфигурации

Возможность повторного использования одного и того же программного обеспечения с различными транспортными средствами — одно из главных преимуществ SDV. Программное обеспечение разрабатывается один раз, а затем используется на нескольких транспортных средствах, и мы можем корректировать его работу в зависимости от специфики автомобиля. В этом заключается основная цель ConCal, который рассчитывает конфигурацию сервиса на основе свойств автомобиля с помощью переопределений конфигурации.

ConfigOverride — это сообщение protobuf, описывающее, как настроить конфигурацию под конкретный автомобиль. Оно состоит из идентификатора переопределения (Override ID), который однозначно определяется предоставляющей его организацией, идентификатора конфигурации и списка пар ConfigOverrideKeyValuePair . ConfigOverride может быть предоставлен только в процессе обновления и только разрешенными службами, которые моделируются производителем оборудования (OEM). Определения protobuf для обеих структур приведены ниже.

// 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 поддерживает следующие операции:

  • Присвоение нового значения: последнее значение устарело, и параметру присваивается новое значение, например, присваивается новое значение простому полю (int, string, float, bool, bytes) или перезаписываются сложные поля, такие как карты, списки, структуры или полная конфигурация.

  • Удаление или очистка значения: эта операция доступна для всех типов данных, включая сообщения, повторяющиеся поля, карты, отдельные поля и саму конфигурацию. Операция может быть выполнена для всего поля или сообщения целиком, что означает, что удаление отдельных ключей в карте и отдельных элементов из повторяющегося поля не поддерживается.

  • Добавить новое значение в карту.

  • Перезаписать значение существующего ключа в карте (эту операцию также можно выполнить как добавление нового значения в карту, если ключ не существует).

Настройте пакет служб с помощью ConCal.

В этой главе описывается, как разработать пакет сервисов, который получает свою конфигурацию во время выполнения. С точки зрения настраиваемого пакета, совершенно неясно, является ли полученная конфигурация заводской настройкой по умолчанию или последующей модификацией с использованием процессов переопределения и калибровки ConCal.

Пример, на основе которого составлена ​​документация, можно найти по адресу system/software_defined_vehicle/samples/concal/src/concal_client .

Более подробную информацию см. в разделе «Разработка пакетов служб» .

Укажите тип конфигурации, принадлежащей пакету служб.

Пакет служб определяет тип получаемой конфигурации. Это позволяет независимо обновлять настраиваемый пакет на основе сохраненных данных конфигурации.

  1. Создайте protobuf-файл (с расширением .proto ), в котором будет указан тип конфигурации:

    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. Создайте цель сборки, которая генерирует библиотеку времени выполнения, позволяющую получать типы конфигурации. В файле 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",
    }
    

Сгенерируйте код промежуточного ПО ConCal RPC для вашего пакета.

Добавьте объявление VSIDL в пакет сервиса:

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

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

Это означает, что пакет является клиентом службы регистрации и получения конфигурации ConCal. Обзор использования VSIDL и промежуточного ПО для генерации привязок RPC-клиента для пакета см. в соответствующем разделе.

Инициализация промежуточного ПО для получения конфигурации RPC.

Во время выполнения инициализируйте компоненты промежуточного программного обеспечения, необходимые для вызовов ConCal RPC. В этом примере мы асинхронно инициализируем их при запуске пакета.

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

Где:

  • В on_start мы создаём среду выполнения Tokio и запускаем задачу Tokio. Задача вызывает setup_register_rpc , прежде чем продолжить выполнение основной логики пакета.
  • Функция setup_register_rpc настраивает привязку RPC к промежуточному ПО. Обратите внимание, что в примере не предполагается, что функциональность ConCal реализована агентом: сервер может стать доступен только после запуска пакета. Поэтому в примере кода ожидается регистрация RPC-сервера с использованием API обнаружения служб.

Инициализация конфигурации

Зарегистрируйте артефакт конфигурации, указав серверу ConCal идентификатор, схему конфигурации и значения конфигурации по умолчанию, заданные фабрикой.

Если регистрация вызывается впервые, значение по умолчанию сохраняется. Если регистрация вызывается при последующих запусках пакета, будет получено сохраненное значение по умолчанию с примененными переопределениями ConCal (если таковые имеются).

Вызов функции настройки регистрации ConCal никогда не завершается с ошибкой, независимо от того, зарегистрирован ли артефакт уже.

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

Где:

  • Задача, запущенная в on_start , после получения RPC-привязки продолжает выполнение бизнес-логики, вызывая sample_concal_main .
  • sample_concal_main начинается с регистрации конфигурационного артефакта. Логика содержится в register_config .
  • Для регистрации конфигурации пакет должен указывать свой тип protobuf, идентификатор и значение по умолчанию.
  • config_fd — это тип конфигурации. Пакет, владеющий этим типом конфигурации, гарантирует, что ожидаемая пакетом схема всегда будет получена, в том числе после обновлений APEX.
  • Идентификатор используется в качестве идентификатора во внутренней логике сохранения данных сервера ConCal.
  • Значение по умолчанию формируется в get_rear_view_camera_factory_config . Значение по умолчанию — это значение, сохраненное сервером ConCal, если ранее ничего не было сохранено. Это один из способов, с помощью которого система может задавать заводские конфигурации. Возможны и другие настройки.

Получить конфигурацию

После регистрации получите конфигурацию. Поскольку конфигурация была зарегистрирована ранее, этот вызов гарантированно будет успешным.

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

Где:

  • Вызов GetConfigRequest гарантированно будет успешным, поскольку конфигурация была зарегистрирована ранее тем же пакетом. Функция Setup позволяет сервисному пакету непрозрачно обрабатывать как случаи фабричных конфигураций, так и переопределенных конфигураций: бизнес-логика сервисного пакета остается неизменной.

  • Вызов функции GetConfigRequest возвращает необработанные байты. Функция get_config продолжает их разбор, преобразуя в ожидаемый тип конфигурации.