Руководство по архитектуре и реализации пользовательских настроек

На этой странице представлено руководство по архитектуре системы пользовательских настроек SDV, а также инструкции по реализации управляемого пользователем сервиса и клиента.

Обзор архитектуры

Система пользовательских настроек отделяет хранение и управление настройками пользователя от их применения и обеспечения соблюдения. Ключевые архитектурные термины суммированы в таблице:

Особенность Описание Роль Ответственность
Сервис, управляемый пользователем Стандартный пакет услуг SDV, предназначенный для управления определенной областью (например, системами отопления, вентиляции и кондиционирования, детскими автокреслами, аудиосистемами). Предоставляет информацию о предполагаемом состоянии возможностей и ограничений оборудования или подсистемы, которыми оно управляет.
  • Регистрация: Информирует агент пользовательских настроек о поддерживаемых им параметрах (метаданные, значения по умолчанию, ограничения).
  • Обеспечение соблюдения правил и автономность: получает запросы на изменение настроек, проверяет их на соответствие текущему состоянию и правилам безопасности и применяет их к оборудованию. Поддерживает полную автономность и определяет, принимается или отклоняется изменение настроек.
Агент пользовательских настроек Центральный координатор Предоставляет централизованное хранилище, управление профилями пользователей и центр уведомлений.
  • Хранилище: Сохраняет настройки для каждого пользователя (например, водителя, пассажира).
  • Маршрутизация: Прокси-серверы перенаправляют запросы от клиентов (например, человеко-машинного интерфейса (HMI)) к соответствующей управляемой пользователем службе.
  • Уведомления: Передает информацию об изменениях заинтересованным сторонам (пользователям интерфейсов или пользователям HMI) с помощью интерфейса ChangeNotifier .
  • Управление пользователями: Обеспечивает переключение пользователей и применение корректного постоянного состояния ко всем зарегистрированным сервисам.
Клиент пользовательских настроек Приложение или сервис, предоставляющий пользовательский интерфейс, например, приложение IVI с интерфейсом человек-машина или другой логикой, которая должна взаимодействовать с предпочтениями пользователя. Взаимодействует с пользователями, отображает настройки и инициирует запросы на изменения.
  • Дисплей: Отображает пользователю текущие настройки и ограничения.
  • Запрос изменений: отправляет инициированные пользователем изменения настроек агенту пользовательских настроек.
  • Получайте уведомления: Подписывается на обновления настроек в режиме реального времени и реагирует на них.

Реализуйте сервис, управляемый пользователем.

To inform the User Preferences agent which keys exist, their data types, default values, and constraints (for example, minimum and maximum values), your service bundle must interact with the UserPreferencesRegistryService interface. When your service starts, it must register the settings it exposes. The agent calls RequestSettingsChange on your service when a user tries to modify a setting (or when switching users).

Выполните действия, описанные в этом разделе, чтобы разрешить пользовательским настройкам (и пользователю с интерфейсом HMI) управлять вашей службой.

  1. Определите интерфейсы сервисов в файле VSIDL:

    • В качестве сервера реализуйте com.sdv.google.user_preferences.user_controllable.UserControllableService . Это позволит агенту отправлять вам запросы на изменение.
    • В качестве клиента используйте com.sdv.google.user_preferences.UserPreferencesRegistryService . Это зарегистрирует ваши настройки при запуске системы.

    В следующем примере показан service_bundle.vsidl для сервиса, управляемого пользователем:

    service_bundle {
        name: "MyFeatureService"
        server {
            service: "com.sdv.google.user_preferences.user_controllable.UserControllableService"
        }
        client {
            service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
        }
    }
    
  2. Зарегистрируйте параметры запуска в ключевом протоколе user_preferences_registry_service.proto :

    1. Подключитесь к UserPreferencesRegistryService .
    2. Создайте экземпляр класса RegisterSettingsRequest .
    3. Определите экземпляр SettingsGroup (логический набор настроек).
    4. Для каждого параметра определите:

      *   **Key:** Unique string ID (for example, `TEMPERATURE`)
      *   **Kind:** `PER_USER` (stored per user profile) or `SHARED`
          (global)
      *   **Default value:** Initial value if no user preference exists
      *   **Constraints:** (optional) Validation rules (for example, Min
          16, Max 32 for HVAC).
      
    5. Вызовите функцию RegisterSettings() .

    Следующий пример представлен на концептуальном языке Rust:

    let temperature_setting = SettingDefinition {
        name: "TEMPERATURE".to_string(),
        kind: SettingKind::PER_USER.into(),
        default_value: Value::Int64(22), // Default 22 degrees
        constraint: Some(Constraints::Int64Constraints(Int64Constraints {
            min_value: Some(16),
            max_value: Some(32),
            ..Default::default()
        })),
        ..Default::default()
    };
    
    registry_client.RegisterSettings(&RegisterSettingsRequest {
        group_name: "HVAC".to_string(),
        version: "1.0".to_string(),
        settings_definitions: vec![temperature_setting],
    }).await?;
    
  3. Обработка запросов на изменение настроек в файле user_controllable_service.proto :

    1. Реализуйте RPC- RequestSettingsChange .
    2. Проверьте, являются ли запрошенные значения допустимыми в текущем контексте (например, готовы ли к работе оборудование).
    3. Для применения изменений используйте определенную логику (например, переместите сиденье, измените скорость вентилятора).
    4. Возвращает примененные значения в RequestSettingsChangeResponse :
    5. Если вы приняли изменение, верните новое значение.
    6. Если вы отклонили или ограничили значение, верните установленное (или сохраненное) значение.

    Следующий пример представлен на концептуальном языке Rust:

    async fn RequestSettingsChange(
        &self,
        _caller_id: ServiceFqin,
        request: &RequestSettingsChangeRequest
    ) -> SdvResult<RequestSettingsChangeResponse> {
        let mut applied_settings = Vec::new();
    
        for setting in &request.settings {
            if self.hardware.set_value(setting.key, setting.value).is_ok() {
                // Change accepted
                applied_settings.push(setting.clone());
            } else {
                // Change rejected, return current actual value
                let current_val = self.hardware.get_value(setting.key);
                applied_settings.push(create_setting(setting.key, current_val));
            }
        }
    
        Ok(RequestSettingsChangeResponse {
            settings: applied_settings,
        })
    }
    
  4. Реализуйте RPC- FactoryReset , чтобы вернуть вашу подсистему в чистое состояние:

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

Обратная совместимость и эволюция схемы

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

Совместимые изменения

Сервис, управляемый пользователем, должен вносить в свою схему настроек только совместимые изменения. Эти изменения гарантируют, что старые клиенты будут продолжать корректно работать без обновлений:

  • Добавьте новый, необязательный параметр с разумным значением по умолчанию:

    • Этот параметр является необязательным, поэтому клиентам необходимо проверять его наличие для поддержки различных версий сервиса.
    • Клиенты игнорируют эту настройку, если они не были обновлены и не распознают её.
    • Если пользовательские настройки для нового параметра отсутствуют, агент использует предоставленное значение по умолчанию.

Несовместимые изменения

Следующие изменения считаются критическими и запрещены, поскольку они немедленно ставят под угрозу безопасность более старых клиентов:

  • Удалить существующую настройку.

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

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

Обработка необходимых изменений, нарушающих обратную совместимость.

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

  1. Создайте новый параметр с желаемым новым определением, ключом или единицей измерения.
  2. Удалите существующую настройку, зарегистрировав её для обеспечения совместимости. Используйте эту новую настройку при разработке новых клиентов.
  3. Посоветуйте новым клиентам внедрить логику совместимости в бизнес-логику сервиса, управляемого пользователем.

Реализация RequestSettingsChange в сервисе должна гарантировать, что изменение одной настройки отразится в её устаревшем аналоге, а изменение аналога отразится в новой настройке.

Пример логики обеспечения совместимости:

  1. В новой службе устаревает старая настройка TEMPERATURE_C (Цельсий) и вводится TEMPERATURE_K (Кельвин).

  2. Когда агент отправляет запрос на обновление новой настройки:

    • Сервис получает запрос на значение TEMPERATURE_K (например, 295,15 K).

    • Логика сервиса преобразует это значение в градусы Цельсия (22°C) и внутренне обновляет и сохраняет оба значения для обеспечения согласованности для устаревших клиентов.

  3. Когда агент отправляет запрос на устаревшую настройку от устаревшего клиента:

    • Сервис получает запрос на значение TEMPERATURE_C (например, 24°C).
    • Сервис преобразует это значение в Кельвины (297,15 К) и внутренне обновляет и сохраняет оба значения.

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

Реализация клиентского приложения

Для реализации клиента (например, человеко-машинного интерфейса), взаимодействующего с агентом пользовательских настроек:

  1. Определите пакет клиентских служб для взаимодействия с конкретными интерфейсами пользовательских настроек. В вашем VSIDL-файле:

    • В качестве сервера реализуйте интерфейс com.sdv.google.user_preferences.view.ChangeNotifier , чтобы агент мог отправлять вашему клиенту уведомления в режиме реального времени об изменениях настроек.
    • В качестве клиента используйте com.sdv.google.user_preferences.UserPreferencesManagementService для запроса изменений настроек и подписки на обновления.
    • В качестве клиента используйте com.sdv.google.user_preferences.UserPreferencesAdminService для управления профилями пользователей (например, создание, выбор, удаление, сброс до заводских настроек).

    В следующем примере показан файл service_bundle.vsidl для клиента:

    service_bundle {
        name: "MyHmiClient"
        server {
            service: "com.sdv.google.user_preferences.view.ChangeNotifier"
        }
        client {
            service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
        }
        client {
            service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
        }
    }
    

    Добавьте соответствующие политики авторизации. Чтобы разрешить взаимодействие агента пользовательских настроек, создайте клиент для указанных серверов. Например:

    server {
        service: "com.sdv.google.user_preferences.view.ChangeNotifier"
        allow_all_channels: true
    }
    client {
        service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
        allow_all_channels: true
    }
    client {
        service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
        allow_all_channels: true
    }
    
  2. Изменить параметры запроса можно в UserPreferencesManagementService :

    1. Подключитесь к UserPreferencesManagementService ).
    2. Создайте экземпляр класса RequestSettingsChangeRequest , указав SettingsGroupId и необходимые параметры.
    3. Необязательно. Установите для параметра ChangePersistencePolicy значение PERSISTENT_CHANGE (по умолчанию) или NON_PERSISTENT_CHANGE .
    4. Вызовите метод RequestSettingsChange() .

    Следующий пример представлен на концептуальном языке Rust:

    management_client.RequestSettingsChange(&RequestSettingsChangeRequest {
        settings_group_id: Some(SettingsGroupId {
            service_fqin: hvac_service_fqin.to_string(),
            name: "HVAC".to_string(),
            ..Default::default()
        }).into(),
        settings: vec![Setting {
            key: "TEMPERATURE".to_string(),
            value: Some(Value::Int64(24)),
            ..Default::default()
        }],
        change_persistence_policy: ChangePersistencePolicy::PERSISTENT_CHANGE.into(),
        ..Default::default()
    }).await?;
    
  3. Подпишитесь на изменения настроек с помощью файлов user_preferences_management_service.proto и change_notifier.proto :

    1. Реализуйте RPC- OnSettingsChange из интерфейса ChangeNotifier в вашем пакете сервиса. Агент пользовательских настроек вызывает этот метод при возникновении изменений.
    2. Подключитесь к UserPreferencesManagementService ).
    3. Создайте запрос SubscribeToSettingsChangeAndGetSettingsRequest указав SettingsGroupId который вы хотите отслеживать.
    4. Чтобы получить состояние настроек и затем отправлять будущие обновления через реализацию метода OnSettingsChange , вызовите SubscribeToSettingsChangeAndGetSettings() .

    Следующий пример реализации интерфейса ChangeNotifier представлен на языке Rust в концептуальном виде:

    #[async_trait]
    impl ChangeNotifier for MyHmiServiceImpl {
        async fn OnSettingsChange(
            &self,
            _caller_id: ServiceFqin,
            request: &OnSettingsChangeRequest,
        ) -> SdvResult<OnSettingsChangeResponse> {
            // Process the active_settings, pending_changes, and persisted_settings
            // Update your UI or internal state accordingly.
            info!("Received settings change for group: {}", request.settings_group_id.name);
            // ...
            Ok(OnSettingsChangeResponse::new())
        }
    }
    

    Следующий пример подписки представлен в концептуальном виде на языке Rust:

    management_client.SubscribeToSettingsChangeAndGetSettings(&SubscribeToSettingsChangeAndGetSettingsRequest {
        settings_group_id: Some(SettingsGroupId {
            service_fqin: hvac_service_fqin.to_string(),
            name: "HVAC".to_string(),
            ..Default::default()
        }).into(),
        ..Default::default()
    }).await?;
    
  4. Необязательно: Клиенты, управляющие профилями пользователей (например, специализированное приложение настроек), могут использовать user_preferences_admin_service.proto для взаимодействия со службой администрирования:

    1. Подключитесь к UserPreferencesAdminService .
    2. Для управления профилями пользователей используйте RPC-вызовы, такие как CreateUser , SelectUser , DeleteUser , FactoryReset и ListUsers .

    Следующий пример создания пользователя представлен на концептуальном языке Rust:

    admin_service_client.CreateUser(&CreateUserRequest {
        user: Some(User {
            id: 1,
            flags: UserFlags::DRIVER.value(),
            ..Default::default()
        }).into(),
        ..Default::default()
    }).await?;
    
  5. Доступ к настройкам и пользователям можно получить через административную службу с помощью файла user_preferences_admin_service.proto :

    1. Чтобы получить список всех зарегистрированных пользователей, вызовите ListUsers() в классе UserPreferencesAdminService .
    2. Чтобы получить настройки для конкретного пользователя, вызовите GetUserSettings(user_id) в классе UserPreferencesAdminService .

    Следующий пример отображения списка пользователей и получения настроек представлен на языке Rust в концептуальном виде:

    // List all users
    let list_users_response = admin_service_client.ListUsers(&ListUsersRequest::new()).await?;
    info!("Available users: {:?}", list_users_response.users);
    
    // Get settings for a specific user (for example, user with ID 1)
    if let Some(user_id) = list_users_response.users.first().map(|u| u.id) {
        let get_settings_response = admin_service_client.GetUserSettings(&GetUserSettingsRequest {
            user_id,
            ..Default::default()
        }).await?;
        info!("Settings for user {}: {:?}", user_id, get_settings_response.groups);
    }
    

Сводка по потоку

  1. Клиент запускает и подключается к услугам управления и администрирования агента.
  2. Клиент обнаруживает группы настроек, подписывается на них и отображает исходное состояние.
  3. Клиент отправляет агенту RequestSettingsChange .
  4. Агент отправляет клиенту уведомление OnSettingsChange с обновленными настройками и состоянием.

Полный рабочий пример см. в HMIService в @samples/user_preferences/v1/ .

Реализуйте агента.

Пакет сервисов, запускаемый оркестратором, реализует агент пользовательских настроек. Для удобства предоставляется эталонная реализация.

Кроме того, предоставляется пример файла user_preferences_sample.vsidl для агента, являющегося образцом пакета сервисов:

package: "com.sdv.oem.user_preferences"

service_bundle {
    name: "UserPreferencesServiceBundle"
    server {
        service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
    }
    server {
        service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
    }
    server {
        service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
    }
    client {
        service: "com.sdv.google.user_preferences.view.ChangeNotifier"
    }
    client {
        service: "com.sdv.google.user_preferences.user_controllable.UserControllableService"
    }
}

Реализация может использовать сгенерированное промежуточное ПО и должна компилироваться в объект rust_ffi_shared , на который ссылается пакет сервиса, реализующий агент пользовательских настроек:

sdv_service_bundle_metadata {
  # This name must match the agent's Bundle Name
  name: "UserPreferencesServiceBundle"
  version_number: 1
  version_name: "1"

  native_library_path: "lib64/<USER-PREFERENCES-FFI-LIB>.so"

  orchestration_config_path: "etc/user_preferences_service_bundle/<USER-PREFERENCES-ORCHESTRATION>.textproto"

  authorization_policy_path: "etc/user_preferences_service_bundle/permissions.textproto"
}

Для работы файла политики авторизации клиента .textproto требуются необходимые разрешения:

client {
    service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
    allow_all_channels: true
}

client {
    service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
    allow_all_channels: true
}

client {
    service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
    allow_all_channels: true
}

Приложение: ключевые понятия

В этом разделе описаны некоторые ключевые понятия.

Пользователи

Каждый пользователь транспортного средства (класс User ) может создать учетную запись, в которой он может хранить свои настройки. В разделе «Настройки пользователя» поддерживаются учетные записи только для водителей транспортных средств. Водители-гости могут создавать временные учетные записи, которые автоматически удаляются после однократного использования.

message User {
  // Required.
  // A unique ID for the user.
  int32 id = 1;

  // Bit flags that define properties of user. Integer values have to be powers of 2 as they are used as
  // a bit mask.
  enum UserFlags {
    // Due to Protobuff requirement to have the first enum option set to 0,
    // Assign 0 to an unused flag
    UNSET = 0x0;
    // Marks the user as vehicle driver
    DRIVER = 0x01;
    // Ephemeral users have non-persistent state, once another user is selected
    // the profile is deleted automatically
    EPHEMERAL = 0x02;
  }

  // Required.
  // Bitmask for the user flags defined above
  int32 flags = 2;
}

Группы настроек

Группы настроек (класс SettingsGroup ) организуют и управляют связанными настройками. Каждый настраиваемый компонент в автомобиле определяет свои настройки как группы, состоящие из списка настроек, представленных в виде пар ключ-значение. Например, в автомобилях с электрическими сиденьями можно определить группу настроек для водительского сиденья и другую для переднего пассажирского сиденья, причем обе группы содержат настройки с одинаковыми названиями.

message SettingsGroup {
  // Required.
  // The identifier for this group.
  SettingsGroupId id = 1;

  // Required.
  // The version number of schema used by this setting group.
  string version = 2;

  // Required.
  // The list of settings within the setting group.
  repeated Setting settings = 3;
}

Настройки

Параметр message (класс Setting ) представляет собой отдельный параметр в составе SettingsGroup . Это сообщение состоит из ключа, который является именем параметра, и значения, которое может быть одного из нескольких типов.

В этом примере показано, как параметр представляется с помощью ключа и значения:

// A key value pair representing a setting within a UserControllableService SettingsGroup.
message Setting {
  // Required.
  // A name that uniquely identifies a setting within a SettingsGroup.
  string key = 1;

  // Required.
  // New value of the setting.
  oneof value {
    bool bool = 2;
    float float = 3;
    int32 int32 = 4;
    int64 int64 = 5;
    bytes blob = 6;
    int32 enum = 7;
  }
}

Определение параметров настройки

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

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

// The definition of a setting, along with its properties such as type and constraints
message SettingDefinition {
  // Required.
  SettingKind kind = 1;

  // Required.
  SettingWithConstraints setting_with_constraints = 2;
}

enum SettingKind {
  // A setting which is applied for all vehicle users.
  SHARED = 0;
  // Store the value of the setting for each vehicle user separately.
  PER_USER = 1;
  // UserControllableService is fully responsible for the storage of PASSTHROUGH setting.
  // User Preferences only notifies UserControllableService when the value is explicitly set by
  // user. User Preferences does not attempt to request changes based on user change or service
  // registration.
  PASSTHROUGH = 2;
};

message SettingWithConstraints {
  // Required
  Setting setting = 1;

  // Required.
  // Defines restrictions on the setting's value
  oneof constraints {
    FloatConstraints float_constraints = 2;
    Int32Constraints int32_constraints = 3;
    Int64Constraints int64_constraints = 4;
    EnumConstraints enum_constraints = 5;
  }
}

message Int32Constraints {
  // The minimum value that a setting can have.
  optional int32 min_value = 1;
  // The maximum value that a setting can have.
  optional int32 max_value = 2;
  // The step by which a setting's value can be increased or decreased.
  optional int32 step = 3;
}

message Int64Constraints {
  // The minimum value that a setting can have.
  optional int64 min_value = 1;
  // The maximum value that a setting can have.
  optional int64 max_value = 2;
  // The step by which a setting's value can be increased or decreased.
  optional int64 step = 3;
}

message FloatConstraints {
  // The minimum value that a setting can have.
  optional float min_value = 1;
  // The maximum value that a setting can have.
  optional float max_value = 2;
  // The step by which a setting's value can be increased or decreased.
  optional float step = 3;
}

message EnumConstraints {
  // Required.
  // List of unique values sorted in ascending order.
  repeated int32 possible_values = 1;
}

Пример последовательности событий при обновлении пользователем настроек.

Этот пример иллюстрирует последовательность событий, происходящих, когда пользователь использует автомобильную информационно-развлекательную систему (IVI) на базе Android Automotive OS (AAOS) для обновления настроек:

События, происходящие при изменении пользователем настроек

Рисунок 1. События, происходящие при изменении пользователем настроек.

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

Этот пример иллюстрирует, как пользовательские настройки SDV применяют предпочтительные параметры пользователя при запуске автомобиля:

События, происходящие при запуске автомобиля.

Рисунок 2. События, происходящие при запуске автомобиля.