En esta página, se proporciona una guía sobre la arquitectura del sistema de preferencias del usuario de SDV y se incluyen instrucciones para implementar un servicio y un cliente controlables por el usuario.
Descripción general de la arquitectura
El sistema de preferencias del usuario desacopla el almacenamiento y la administración de la configuración del usuario de la aplicación y el cumplimiento de esa configuración. En la siguiente tabla, se resumen los términos arquitectónicos clave:
| Función | Descripción | Función | Responsabilidad |
|---|---|---|---|
| Servicio controlable por el usuario | Un paquete de servicios de SDV estándar que administra un dominio específico (por ejemplo, HVAC, asientos de automóvil, audio) | Proporciona el estado previsto de las capacidades y las restricciones del hardware o subsistema que controla. |
|
| Agente de preferencias del usuario | El organizador central | Proporciona almacenamiento centralizado, administración de perfiles de usuario y centro de notificaciones. |
|
| Cliente de preferencias del usuario | Una app o un servicio que proporciona una interfaz de usuario, por ejemplo, una app de IVI con una HMI o alguna otra lógica que necesita interactuar con las preferencias del usuario | Interactúa con los usuarios, muestra la configuración e inicia solicitudes de cambio. |
|
Implementa un servicio controlable por el usuario
Para informar al agente de User Preferences qué claves existen, sus tipos de datos, valores predeterminados y restricciones (por ejemplo, valores mínimos y máximos), tu paquete de servicio debe interactuar con la interfaz UserPreferencesRegistryService. Cuando se inicia tu servicio, debe registrar los parámetros de configuración que expone. El agente llama a RequestSettingsChange en tu servicio cuando un usuario intenta modificar un parámetro de configuración (o cuando cambia de usuario).
Sigue los pasos de esta sección para permitir que las preferencias del usuario (y un usuario con HMI) controlen tu servicio.
Define las interfaces de servicio en el archivo VSIDL:
- Como servidor, implementa
com.sdv.google.user_preferences.user_controllable.UserControllableService. Esto permite que el agente te envíe solicitudes de cambio. - Como cliente, consume
com.sdv.google.user_preferences.UserPreferencesRegistryService. Esto registra tu configuración al inicio.
En el siguiente ejemplo, se muestra
service_bundle.vsidlpara un servicio controlado por el usuario:service_bundle { name: "MyFeatureService" server { service: "com.sdv.google.user_preferences.user_controllable.UserControllableService" } client { service: "com.sdv.google.user_preferences.UserPreferencesRegistryService" } }- Como servidor, implementa
Registra la configuración en el inicio del prototipo clave
user_preferences_registry_service.proto:- Conéctate a
UserPreferencesRegistryService. - Crea una instancia de
RegisterSettingsRequest. - Define una instancia de
SettingsGroup(una colección lógica de parámetros de configuración). Para cada parámetro de configuración, define lo siguiente:
* **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).Llamar a
RegisterSettings()
El siguiente ejemplo está en Rust conceptual:
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?;- Conéctate a
Cómo controlar las solicitudes de cambio de configuración en
user_controllable_service.proto:- Implementa la RPC de
RequestSettingsChange. - Valida si los valores solicitados son válidos en el contexto actual (por ejemplo, si el hardware está listo).
- Aplica una lógica específica para aplicar el cambio (por ejemplo, mover el asiento o cambiar la velocidad del ventilador).
- Devuelve los valores aplicados en
RequestSettingsChangeResponse: - Si aceptaste el cambio, devuelve el valor nuevo.
- Si rechazaste o restringiste el valor, devuelve el valor que estableciste (o conservaste).
El siguiente ejemplo está en Rust conceptual:
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, }) }- Implementa la RPC de
Implementa el RPC
FactoryResetpara revertir tu subsistema a un estado limpio:- Restablece todos los parámetros de configuración a sus valores predeterminados (según se definen en la configuración de tu código).
- Llama a
UpdateSettingsen el servicio de registro para informar al agente que los valores cambiaron de forma externa (por el restablecimiento, no por una solicitud del usuario).
Retrocompatibilidad y evolución del esquema
El esquema de la configuración definida por un servicio controlable por el usuario se considera una interfaz pública. El agente de preferencias del usuario y varios clientes (por ejemplo, las HMI) consumen esta interfaz, que puede tener diferentes cronogramas de lanzamiento. Por lo tanto, mantener una estricta retrocompatibilidad es fundamental para evitar la inestabilidad del sistema y la interrupción del cliente.
Cambios compatibles
Un servicio controlable por el usuario solo debe introducir cambios compatibles en su esquema de configuración. Estos cambios garantizan que los clientes más antiguos sigan funcionando correctamente sin actualizaciones:
Agrega un nuevo parámetro de configuración opcional con un valor predeterminado razonable:
- Este parámetro de configuración es opcional, por lo que los clientes deben verificar su presencia para admitir diferentes versiones del servicio.
- Los clientes ignoran este parámetro de configuración si no se actualizaron para reconocerlo.
- Si no existe una preferencia del usuario para el nuevo parámetro de configuración, el agente usa el valor predeterminado proporcionado.
Cambios incompatibles
Los siguientes cambios se consideran rotundos y están prohibidos, ya que comprometen de inmediato a los clientes más antiguos:
Quita un parámetro de configuración existente.
Cambiar el significado, la unidad o el tipo de datos de un parámetro de configuración existente (por ejemplo, cambiar de un número entero que representa la temperatura en grados Celsius a un número de punto flotante que representa la presión en pascales)
Cambiar el valor predeterminado de un parámetro de configuración existente La razón principal para prohibir los cambios en los valores predeterminados es la posible complicación con la migración de datos persistentes. El agente almacena la configuración según el esquema de registro del servicio. Cambiar un valor predeterminado requeriría una lógica compleja para migrar todos los perfiles de usuario existentes al nuevo valor predeterminado o correr el riesgo de usar un valor técnicamente incorrecto para los usuarios que nunca establecieron explícitamente la preferencia.
Controla los cambios rotundos obligatorios
Si se requiere un cambio rotundo, el servicio no debe modificar el parámetro de configuración existente. El enfoque para administrar esta transición y mantener la compatibilidad se encuentra principalmente en la lógica de servicio controlable por el usuario:
- Crea un parámetro de configuración nuevo con la definición, la clave o la unidad nuevas deseadas.
- Registra el parámetro de configuración existente para la compatibilidad y, luego, descarta su uso. Utiliza este nuevo parámetro de configuración cuando desarrolles clientes nuevos.
- Recomienda a los clientes nuevos que implementen lógica de compatibilidad en la lógica empresarial del servicio controlable por el usuario.
La implementación de RequestSettingsChange del servicio debe garantizar que un cambio en un parámetro de configuración se refleje en su contraparte obsoleta, y que un cambio en la contraparte se refleje en el parámetro de configuración.
Ejemplo de lógica de compatibilidad:
Un servicio dejó de usar el parámetro de configuración anterior
TEMPERATURE_C(Celsius) y, en su lugar, introdujoTEMPERATURE_K(Kelvin).Cuando el agente envía una solicitud para actualizar el parámetro de configuración nuevo, sucede lo siguiente:
El servicio recibe una solicitud para
TEMPERATURE_K(por ejemplo, 295.15 K).La lógica del servicio convierte este valor a grados Celsius (22 °C) y, de forma interna, actualiza y conserva ambos valores para mantener la coherencia en los clientes heredados.
Cuando el agente envía una solicitud para el parámetro de configuración en desuso desde un cliente heredado, sucede lo siguiente:
- El servicio recibe una solicitud para
TEMPERATURE_C(por ejemplo, 24 °C). - El servicio convierte este valor a Kelvin (297.15 K) y, de forma interna, actualiza y conserva ambos valores.
- El servicio recibe una solicitud para
Esta estrategia de escritura doble garantiza que todos los clientes, independientemente de su programa de lanzamiento, lean datos coherentes y precisos, lo que evita interrupciones causadas por discrepancias en los lanzamientos externos.
Implementa un cliente
Para implementar un cliente (por ejemplo, una HMI) que interactúe con el agente de User Preferences, haz lo siguiente:
Define tu paquete de servicios del cliente para interactuar con interfaces específicas de User Preferences. En tu archivo VSIDL, haz lo siguiente:
- Como servidor, implementa
com.sdv.google.user_preferences.view.ChangeNotifierpara permitir que el agente envíe a tu cliente notificaciones en tiempo real sobre los cambios en la configuración. - Como cliente, usa
com.sdv.google.user_preferences.UserPreferencesManagementServicepara solicitar modificaciones de la configuración y suscribirte a las actualizaciones. - Como cliente, usa
com.sdv.google.user_preferences.UserPreferencesAdminServicepara administrar perfiles de usuario (por ejemplo, crear, seleccionar, borrar y restablecer la configuración de fábrica).
En el siguiente ejemplo, se muestra un archivo
service_bundle.vsidlpara un cliente: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" } }Agrega las políticas de autorización según corresponda. Para permitir la comunicación del agente de preferencias del usuario, crea un cliente para los servidores especificados. Por ejemplo:
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 }- Como servidor, implementa
Puedes cambiar la configuración de la solicitud en
UserPreferencesManagementService:- Conéctate a
UserPreferencesManagementService. - Crea una instancia de
RequestSettingsChangeRequesty especifica elSettingsGroupIdy la configuración deseada. - Opcional. Establece
ChangePersistencePolicyenPERSISTENT_CHANGE(predeterminado) oNON_PERSISTENT_CHANGE. - Llamar a
RequestSettingsChange()
El siguiente ejemplo está en Rust conceptual:
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?;- Conéctate a
Suscríbete a los cambios de configuración con
user_preferences_management_service.protoychange_notifier.proto:- Implementa la RPC de
OnSettingsChangedesde la interfaz deChangeNotifierdentro de tu paquete de servicios. El agente de User Preferences invoca este método cuando se produce un cambio. - Conéctate a
UserPreferencesManagementService. - Crea un objeto
SubscribeToSettingsChangeAndGetSettingsRequestque especifique el objetoSettingsGroupIdque deseas supervisar. - Para devolver el estado de la configuración y, luego, enviar actualizaciones futuras a través de tu implementación de
OnSettingsChange, llama aSubscribeToSettingsChangeAndGetSettings().
El siguiente ejemplo de implementación de la interfaz
ChangeNotifierestá en Rust conceptual:#[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()) } }El siguiente ejemplo de suscripción está en Rust conceptual:
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?;- Implementa la RPC de
Opcional: Los clientes que administran perfiles de usuario (por ejemplo, una app de configuración dedicada) pueden usar
user_preferences_admin_service.protopara interactuar con el servicio de administrador:- Conéctate a
UserPreferencesAdminService. - Usa RPCs como
CreateUser,SelectUser,DeleteUser,FactoryResetyListUserspara administrar los perfiles de usuario.
El siguiente ejemplo de creación de un usuario está en Rust conceptual:
admin_service_client.CreateUser(&CreateUserRequest { user: Some(User { id: 1, flags: UserFlags::DRIVER.value(), ..Default::default() }).into(), ..Default::default() }).await?;- Conéctate a
Descubre la configuración y los usuarios disponibles a través del servicio de administrador con
user_preferences_admin_service.proto:- Llama a
ListUsers()enUserPreferencesAdminServicepara obtener una lista de todos los usuarios registrados. - Para obtener la configuración de un usuario específico, llama a
GetUserSettings(user_id)en elUserPreferencesAdminService.
El siguiente ejemplo de cómo enumerar usuarios y obtener la configuración está en Rust conceptual:
// 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); }- Llama a
Resumen del flujo
- El cliente se inicia y se conecta a los servicios administrativos y de administración del agente.
- El cliente descubre los grupos de configuración y se suscribe a ellos, y muestra el estado inicial.
- El cliente envía un
RequestSettingsChangeal agente. - El agente envía una notificación
OnSettingsChangeal cliente con la configuración y el estado actualizados.
Para ver un ejemplo completo, consulta HMIService en @samples/user_preferences/v1/.
Implementa el agente
Un paquete de servicios iniciado por el organizador implementa el agente de preferencias del usuario. Para mayor comodidad, se proporciona una implementación de referencia.
Además, se proporciona este archivo user_preferences_sample.vsidl de paquete de servicio de muestra para el agente:
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"
}
}
La implementación puede usar el middleware generado y debe compilarse en un rust_ffi_shared al que hace referencia el paquete de servicio que implementa el agente de preferencias del usuario:
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"
}
El archivo de política de autorización .textproto del cliente requiere los permisos necesarios:
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
}
Apéndice: Conceptos clave
En esta sección, se describen algunos conceptos clave.
Usuarios
Cada usuario del vehículo (clase User) puede crear una cuenta en la que puede almacenar sus preferencias. Las preferencias del usuario solo admiten cuentas para conductores de vehículos.
Los conductores invitados pueden crear cuentas temporales que se borran automáticamente después de un uso.
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;
}
Grupos de configuración
Los grupos de configuración (clase SettingsGroup) organizan y administran la configuración relacionada.
Cada componente configurable del vehículo define sus parámetros de configuración como grupos, que consisten en una lista de parámetros de configuración representados como pares clave-valor. Por ejemplo, los vehículos con asientos eléctricos pueden definir un grupo de configuración para el asiento del conductor y otro para el del pasajero delantero, en los que ambos grupos contienen parámetros de configuración con el mismo nombre.
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;
}
Configuración
El parámetro de configuración, el mensaje (clase Setting), representa un solo parámetro de configuración dentro de SettingsGroup. Este mensaje consta de una clave, que es el nombre del parámetro de configuración, y un valor, que puede ser de varios tipos.
En este ejemplo, se muestra cómo se representa un parámetro de configuración con una clave y un valor:
// 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;
}
}
Cómo configurar definiciones
Las definiciones de configuración (clase SettingDefinition) sirven como plantillas para la configuración individual dentro de un grupo de configuración. Especifican las características fundamentales del parámetro de configuración, como si se comparte entre todos los usuarios, si es específico para cada usuario o si se administra de forma externa (transferencia).
Es importante destacar que los parámetros de configuración también definen las restricciones sobre el valor del parámetro, como los valores mínimos y máximos, los incrementos permitidos o un conjunto restringido de opciones. Estas restricciones garantizan la integridad y la coherencia de los datos para cada parámetro de configuración.
// 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;
}
Ejemplo de secuencia de eventos cuando un usuario actualiza un parámetro de configuración
En este ejemplo, se ilustra la secuencia de eventos que se producen cuando un usuario usa el sistema de infoentretenimiento (IVI) integrado en el vehículo con el SO Android Automotive (AAOS) para actualizar un parámetro de configuración:
Figura 1: Son los eventos que se producen cuando un usuario cambia un parámetro de configuración.
Ejemplo de secuencia de eventos cuando se inicia un vehículo
En este ejemplo, se ilustra cómo las preferencias del usuario de SDV aplican la configuración preferida del usuario cuando se inicia un vehículo:
Figura 2: Eventos cuando se pone en marcha un vehículo.