Cette page fournit un guide sur l'architecture du système de préférences utilisateur SDV, ainsi que des instructions pour implémenter un service et un client contrôlables par l'utilisateur.
Présentation de l'architecture
Le système de préférences utilisateur dissocie le stockage et la gestion des paramètres utilisateur de l'application et de l'application de ces paramètres. Les principaux termes d'architecture sont résumés dans le tableau suivant :
| Fonctionnalité | Description | Rôle | Responsabilité |
|---|---|---|---|
| Service contrôlable par l'utilisateur | Bundle de services SDV standard qui gère un domaine spécifique (par exemple, CVC, sièges auto, audio) | Fournit l'état prévu des fonctionnalités et des contraintes du matériel ou du sous-système qu'il contrôle. |
|
| Agent de préférences utilisateur | Orchestrateur central | Fournit un stockage centralisé, une gestion des profils utilisateur et un hub de notifications. |
|
| Client de préférences utilisateur | Application ou service qui fournit une interface utilisateur, par exemple une application IVI avec une IHM ou une autre logique qui doit interagir avec les préférences utilisateur | Interagit avec les utilisateurs, affiche les paramètres et lance des requêtes de modification. |
|
Implémenter un service contrôlable par l'utilisateur
Pour informer l'agent de préférences utilisateur des clés existantes, de leurs types de données, de leurs valeurs par défaut et de leurs contraintes (par exemple, valeurs minimales et maximales), votre bundle de services doit interagir avec l'interface UserPreferencesRegistryService. Au démarrage de votre service, il doit enregistrer les paramètres qu'il expose. L'agent appelle RequestSettingsChange sur votre service lorsqu'un utilisateur tente de modifier un paramètre (ou lorsqu'il change d'utilisateur).
Suivez les étapes de cette section pour permettre aux préférences utilisateur (et à un utilisateur avec une IHM) de contrôler votre service.
Définissez les interfaces de service dans le fichier VSIDL :
- En tant que serveur, implémentez
com.sdv.google.user_preferences.user_controllable.UserControllableService. Cela permet à l'agent de vous envoyer des requêtes de modification. - En tant que client, utilisez
com.sdv.google.user_preferences.UserPreferencesRegistryService. Cela enregistre vos paramètres au démarrage.
L'exemple suivant montre
service_bundle.vsidlpour un service contrôlable par l'utilisateur :service_bundle { name: "MyFeatureService" server { service: "com.sdv.google.user_preferences.user_controllable.UserControllableService" } client { service: "com.sdv.google.user_preferences.UserPreferencesRegistryService" } }- En tant que serveur, implémentez
Enregistrez les paramètres au démarrage dans le proto de clé
user_preferences_registry_service.proto:- Connectez-vous à
UserPreferencesRegistryService. - Créez une instance de
RegisterSettingsRequest. - Définissez une instance de
SettingsGroup(ensemble logique de paramètres). Pour chaque paramètre, définissez les éléments suivants :
* **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).Appelez
RegisterSettings().
L'exemple suivant est en Rust conceptuel :
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?;- Connectez-vous à
Gérez les requêtes de modification des paramètres dans
user_controllable_service.proto:- Implémentez le RPC
RequestSettingsChange. - Vérifiez si les valeurs demandées sont valides dans le contexte actuel (par exemple, si le matériel est prêt).
- Appliquez une logique spécifique pour appliquer la modification (par exemple, déplacez le siège, modifiez la vitesse du ventilateur).
- Renvoie les valeurs appliquées dans
RequestSettingsChangeResponse: - Si vous avez accepté la modification, renvoyez la nouvelle valeur.
- Si vous avez refusé ou limité la valeur, renvoyez la valeur que vous avez définie (ou conservée).
L'exemple suivant est en Rust conceptuel :
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, }) }- Implémentez le RPC
Implémentez le RPC
FactoryResetpour rétablir l'état initial de votre sous-système :- Réinitialisez tous les paramètres à leurs valeurs par défaut (telles que définies dans la configuration de votre code).
- Appelez
UpdateSettingssur le service de registre pour informer l'agent que les valeurs ont été modifiées en externe (par la réinitialisation, et non par une requête utilisateur).
Rétrocompatibilité et évolution du schéma
Le schéma des paramètres définis par un service contrôlable par l'utilisateur est considéré comme une interface publique. Cette interface est utilisée par l'agent de préférences utilisateur et différents clients (par exemple, les IHM), qui peuvent avoir des calendriers de publication différents. Par conséquent, il est essentiel de maintenir une rétrocompatibilité stricte pour éviter l'instabilité du système et la rupture du client.
Modifications compatibles
Un service contrôlable par l'utilisateur ne doit introduire que des modifications compatibles dans son schéma de paramètres. Ces modifications garantissent que les anciens clients continuent de fonctionner correctement sans mises à jour :
Ajoutez un nouveau paramètre facultatif avec une valeur par défaut raisonnable :
- Ce paramètre est facultatif. Les clients doivent donc vérifier sa présence pour prendre en charge différentes versions du service.
- Les clients ignorent ce paramètre s'ils n'ont pas été mis à jour pour le reconnaître.
- Si aucune préférence utilisateur n'existe pour le nouveau paramètre, l'agent utilise la valeur par défaut fournie.
Modifications incompatibles
Les modifications suivantes sont considérées comme des modifications destructives et sont interdites, car elles compromettent immédiatement les anciens clients :
Supprimez un paramètre existant.
Modifiez la signification, l'unité ou le type de données d'un paramètre existant (par exemple, passez d'un entier représentant la température en degrés Celsius à un flottant représentant la pression en pascals).
Modifiez la valeur par défaut d'un paramètre existant. La principale raison pour laquelle les modifications de la valeur par défaut sont interdites est la complication potentielle liée à la migration des données persistantes. L'agent stocke les paramètres en fonction du schéma d'enregistrement du service. La modification d'une valeur par défaut nécessiterait une logique complexe pour migrer tous les profils utilisateur existants vers la nouvelle valeur par défaut ou risquerait d'utiliser une valeur techniquement incorrecte pour les utilisateurs qui n'ont jamais défini explicitement la préférence.
Gérer les modifications destructives requises
Si une modification destructive est requise, le service ne doit pas modifier le paramètre existant. L'approche permettant de gérer cette transition tout en maintenant la compatibilité se trouve principalement dans la logique du service contrôlable par l'utilisateur :
- Créez un paramètre avec la nouvelle définition, la nouvelle clé ou la nouvelle unité souhaitées.
- Dépréciez le paramètre existant en l'enregistrant pour la compatibilité. Utilisez ce nouveau paramètre lorsque vous développez de nouveaux clients.
- Conseillez aux nouveaux clients d'implémenter une logique de compatibilité dans la logique métier du service contrôlable par l'utilisateur.
L'implémentation RequestSettingsChange du service doit garantir qu'une modification apportée à un paramètre est reflétée dans son homologue obsolète, et qu'une modification apportée à l'homologue est reflétée dans le paramètre.
Exemple de logique de compatibilité :
Un service déprécie l'ancien paramètre
TEMPERATURE_C(Celsius) et introduitTEMPERATURE_K(Kelvin).Lorsque l'agent envoie une requête pour mettre à jour le nouveau paramètre :
Le service reçoit une requête pour
TEMPERATURE_K(par exemple, 295,15 K).La logique du service convertit cette valeur en degrés Celsius (22 °C), puis met à jour et conserve les deux valeurs en interne pour maintenir la cohérence des clients hérités.
Lorsque l'agent envoie une requête pour le paramètre obsolète à partir d'un client hérité :
- Le service reçoit une requête pour
TEMPERATURE_C(par exemple, 24 °C). - Le service convertit cette valeur en kelvins (297,15 K), puis met à jour et conserve les deux valeurs en interne.
- Le service reçoit une requête pour
Cette stratégie de double écriture garantit que tous les clients, quel que soit leur calendrier de publication, lisent des données cohérentes et précises, évitant ainsi les ruptures causées par des incompatibilités de publication externes.
Implémenter un client
Pour implémenter un client (par exemple, une IHM) qui interagit avec l'agent de préférences utilisateur :
Définissez votre bundle de services client pour interagir avec des interfaces de préférences utilisateur spécifiques. Dans votre fichier VSIDL :
- En tant que serveur, implémentez
com.sdv.google.user_preferences.view.ChangeNotifierpour permettre à l'agent d'envoyer à votre client des notifications en temps réel concernant les modifications de paramètres. - En tant que client, utilisez
com.sdv.google.user_preferences.UserPreferencesManagementServicepour demander des modifications de paramètres et vous abonner aux mises à jour. - En tant que client, utilisez
com.sdv.google.user_preferences.UserPreferencesAdminServicepour gérer les profils utilisateur (par exemple, créer, sélectionner, supprimer, réinitialiser les paramètres d'usine).
L'exemple suivant montre un fichier
service_bundle.vsidlpour un client :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" } }Ajoutez les règles d'autorisation en conséquence. Pour autoriser la communication de l'agent de préférences utilisateur, créez un client pour les serveurs spécifiés. Exemple :
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 }- En tant que serveur, implémentez
Vous pouvez modifier les paramètres de requête dans
UserPreferencesManagementService:- Connectez-vous à
UserPreferencesManagementService. - Créez une instance de
RequestSettingsChangeRequest, en spécifiant leSettingsGroupIdet les paramètres souhaités. - Facultatif. Définissez
ChangePersistencePolicysurPERSISTENT_CHANGE(par défaut) ouNON_PERSISTENT_CHANGE. - Appelez
RequestSettingsChange().
L'exemple suivant est en Rust conceptuel :
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?;- Connectez-vous à
Abonnez-vous aux modifications de paramètres avec
user_preferences_management_service.protoetchange_notifier.proto:- Implémentez le RPC
OnSettingsChangeà partir de l'interfaceChangeNotifierdans votre bundle de services. L'agent de préférences utilisateur appelle cette méthode lorsqu'une modification se produit. - Connectez-vous à
UserPreferencesManagementService. - Créez un
SubscribeToSettingsChangeAndGetSettingsRequesten spécifiant leSettingsGroupIdque vous souhaitez surveiller. - Pour renvoyer l'état des paramètres, puis envoyer les futures mises à jour via votre implémentation
OnSettingsChange, appelezSubscribeToSettingsChangeAndGetSettings().
L'exemple suivant d'implémentation de l'interface
ChangeNotifierest en Rust conceptuel :#[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()) } }L'exemple suivant d'abonnement est en Rust conceptuel :
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?;- Implémentez le RPC
Facultatif : les clients qui gèrent les profils utilisateur (par exemple, une application de paramètres dédiée) peuvent utiliser
user_preferences_admin_service.protopour interagir avec le service d'administration :- Connectez-vous à
UserPreferencesAdminService. - Utilisez des RPC tels que
CreateUser,SelectUser,DeleteUser,FactoryResetetListUserspour gérer les profils utilisateur.
L'exemple suivant de création d'un utilisateur est en Rust conceptuel :
admin_service_client.CreateUser(&CreateUserRequest { user: Some(User { id: 1, flags: UserFlags::DRIVER.value(), ..Default::default() }).into(), ..Default::default() }).await?;- Connectez-vous à
Découvrez les paramètres et les utilisateurs disponibles via le service d'administration avec
user_preferences_admin_service.proto:- Obtenez la liste de tous les utilisateurs enregistrés en appelant
ListUsers()surUserPreferencesAdminService. - Obtenez les paramètres d'un utilisateur spécifique en appelant
GetUserSettings(user_id)surUserPreferencesAdminService.
L'exemple suivant de liste des utilisateurs et d'obtention des paramètres est en Rust conceptuel :
// 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); }- Obtenez la liste de tous les utilisateurs enregistrés en appelant
Résumé du flux
- Le client démarre et se connecte aux services de gestion et d'administration de l'agent.
- Le client découvre les groupes de paramètres, s'y abonne et affiche l'état initial.
- Le client envoie un
RequestSettingsChangeà l'agent. - L'agent envoie une notification
OnSettingsChangeau client avec les paramètres et l'état mis à jour.
Pour obtenir un exemple de fonctionnement complet, consultez HMIService dans @samples/user_preferences/v1/.
Implémenter l'agent
Un bundle de services lancé par l'orchestrateur implémente l'agent de préférences utilisateur. Pour plus de commodité, une implémentation de référence est fournie.
De plus, cet exemple de bundle de services user_preferences_sample.vsidl pour l'agent est fourni :
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"
}
}
L'implémentation peut utiliser le middleware généré et doit être compilée dans un rust_ffi_shared référencé par le bundle de services qui implémente l'agent de préférences utilisateur :
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"
}
Le fichier de la règle d'autorisation .textproto du client nécessite les autorisations nécessaires :
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
}
Annexe : concepts clés
Cette section décrit quelques concepts clés.
Utilisateurs
Chaque utilisateur du véhicule (classe User) peut créer un compte dans lequel il peut stocker ses préférences. Les préférences utilisateur ne sont compatibles qu'avec les comptes des conducteurs de véhicules.
Les conducteurs invités peuvent créer des comptes temporaires qui sont automatiquement supprimés après une seule utilisation.
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;
}
Groupes de paramètres
Les groupes de paramètres (classe SettingsGroup) organisent et gèrent les paramètres associés.
Chaque composant configurable du véhicule définit ses paramètres sous forme de groupes, qui se composent d'une liste de paramètres représentés sous forme de paires clé/valeur. Par exemple, les véhicules équipés de sièges électriques peuvent définir un groupe de paramètres pour le siège du conducteur et un autre pour le passager avant, les deux groupes contenant des paramètres portant le même nom.
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;
}
Paramètres
Le paramètre, message (Setting classe), représente un seul paramètre dans
SettingsGroup. Ce message se compose d'une clé, qui est le nom du paramètre, et d'une valeur, qui peut être de plusieurs types.
Cet exemple montre comment un paramètre est représenté avec une clé et une valeur :
// 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;
}
}
Définitions des paramètres
Les définitions des paramètres (classe SettingDefinition) servent de modèles pour les paramètres individuels d'un groupe de paramètres. Ils spécifient les caractéristiques fondamentales du paramètre, par exemple s'il est partagé entre tous les utilisateurs, spécifique à chaque utilisateur ou géré en externe (passthrough).
Il est important de noter que les définitions des paramètres définissent également toutes les contraintes sur la valeur du paramètre, telles que les valeurs minimales et maximales, les incréments autorisés ou un ensemble d'options limité. Ces contraintes garantissent l'intégrité et la cohérence des données pour chaque paramètre.
// 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;
}
Exemple de séquence d'événements lorsqu'un utilisateur modifie un paramètre
Cet exemple illustre la séquence d'événements qui se produisent lorsqu'un utilisateur utilise le système d'infodivertissement embarqué (IVI) Android Automotive OS (AAOS) pour modifier un paramètre :
Figure 1. Événements lorsqu'un utilisateur modifie un paramètre.
Exemple de séquence d'événements au démarrage d'un véhicule
Cet exemple illustre comment les préférences utilisateur SDV appliquent les paramètres préférés d'un utilisateur au démarrage d'un véhicule :
Figure 2. Événements au démarrage d'un véhicule.