Architecture des préférences utilisateur et guide d'implémentation

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éDescriptionRôleResponsabilité
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.
  • Enregistrement : informe l'agent de préférences utilisateur des paramètres qu'il prend en charge (métadonnées, valeurs par défaut, contraintes).
  • Application et autonomie : reçoit les requêtes de modification des paramètres, les valide par rapport à l'état actuel et aux règles de sécurité, puis les applique au matériel. Maintient une autonomie totale et spécifie si une modification de paramètre est acceptée ou refusée.
Agent de préférences utilisateur Orchestrateur central Fournit un stockage centralisé, une gestion des profils utilisateur et un hub de notifications.
  • Stockage : conserve les paramètres par utilisateur (par exemple, conducteur, passager).
  • Routage : proxy les requêtes de modification des clients (par exemple, interface homme-machine (IHM)) vers le service contrôlable par l'utilisateur approprié.
  • Notifications : diffuse les modifications aux parties intéressées (vues ou IHM) à l'aide de l'interface ChangeNotifier.
  • Gestion des utilisateurs : gère le changement d'utilisateur et applique l'état persistant correct à tous les services enregistrés.
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.
  • Affichage : présente les paramètres et les contraintes actuels à l'utilisateur.
  • Demander des modifications : envoie les modifications de paramètres initiées par l'utilisateur à l'agent de préférences utilisateur.
  • Recevoir des notifications : s'abonne aux mises à jour en temps réel des paramètres et y réagit.

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.

  1. 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.vsidl pour 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"
        }
    }
    
  2. Enregistrez les paramètres au démarrage dans le proto de clé user_preferences_registry_service.proto :

    1. Connectez-vous à UserPreferencesRegistryService.
    2. Créez une instance de RegisterSettingsRequest.
    3. Définissez une instance de SettingsGroup (ensemble logique de paramètres).
    4. 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).
      
    5. 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?;
    
  3. Gérez les requêtes de modification des paramètres dans user_controllable_service.proto :

    1. Implémentez le RPC RequestSettingsChange.
    2. Vérifiez si les valeurs demandées sont valides dans le contexte actuel (par exemple, si le matériel est prêt).
    3. Appliquez une logique spécifique pour appliquer la modification (par exemple, déplacez le siège, modifiez la vitesse du ventilateur).
    4. Renvoie les valeurs appliquées dans RequestSettingsChangeResponse :
    5. Si vous avez accepté la modification, renvoyez la nouvelle valeur.
    6. 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,
        })
    }
    
  4. Implémentez le RPC FactoryReset pour rétablir l'état initial de votre sous-système :

    1. Réinitialisez tous les paramètres à leurs valeurs par défaut (telles que définies dans la configuration de votre code).
    2. Appelez UpdateSettings sur 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 :

  1. Créez un paramètre avec la nouvelle définition, la nouvelle clé ou la nouvelle unité souhaitées.
  2. 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.
  3. 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é :

  1. Un service déprécie l'ancien paramètre TEMPERATURE_C (Celsius) et introduit TEMPERATURE_K (Kelvin).

  2. 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.

  3. 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.

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 :

  1. 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.ChangeNotifier pour 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.UserPreferencesManagementService pour demander des modifications de paramètres et vous abonner aux mises à jour.
    • En tant que client, utilisez com.sdv.google.user_preferences.UserPreferencesAdminService pour 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.vsidl pour 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
    }
    
  2. Vous pouvez modifier les paramètres de requête dans UserPreferencesManagementService :

    1. Connectez-vous à UserPreferencesManagementService.
    2. Créez une instance de RequestSettingsChangeRequest, en spécifiant le SettingsGroupId et les paramètres souhaités.
    3. Facultatif. Définissez ChangePersistencePolicy sur PERSISTENT_CHANGE (par défaut) ou NON_PERSISTENT_CHANGE.
    4. 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?;
    
  3. Abonnez-vous aux modifications de paramètres avec user_preferences_management_service.proto et change_notifier.proto :

    1. Implémentez le RPC OnSettingsChange à partir de l'interface ChangeNotifier dans votre bundle de services. L'agent de préférences utilisateur appelle cette méthode lorsqu'une modification se produit.
    2. Connectez-vous à UserPreferencesManagementService.
    3. Créez un SubscribeToSettingsChangeAndGetSettingsRequest en spécifiant le SettingsGroupId que vous souhaitez surveiller.
    4. Pour renvoyer l'état des paramètres, puis envoyer les futures mises à jour via votre implémentation OnSettingsChange, appelez SubscribeToSettingsChangeAndGetSettings().

    L'exemple suivant d'implémentation de l'interface ChangeNotifier est 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?;
    
  4. 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.proto pour interagir avec le service d'administration :

    1. Connectez-vous à UserPreferencesAdminService.
    2. Utilisez des RPC tels que CreateUser, SelectUser, DeleteUser, FactoryReset et ListUsers pour 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?;
    
  5. Découvrez les paramètres et les utilisateurs disponibles via le service d'administration avec user_preferences_admin_service.proto :

    1. Obtenez la liste de tous les utilisateurs enregistrés en appelant ListUsers() sur UserPreferencesAdminService.
    2. Obtenez les paramètres d'un utilisateur spécifique en appelant GetUserSettings(user_id) sur UserPreferencesAdminService.

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

Résumé du flux

  1. Le client démarre et se connecte aux services de gestion et d'administration de l'agent.
  2. Le client découvre les groupes de paramètres, s'y abonne et affiche l'état initial.
  3. Le client envoie un RequestSettingsChange à l'agent.
  4. L'agent envoie une notification OnSettingsChange au 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 :

Événements lorsque l&#39;utilisateur modifie 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 :

Événements lorsque le véhicule démarre

Figure 2. Événements au démarrage d'un véhicule.