Na tej stronie znajdziesz przewodnik po architekturze systemu preferencji użytkownika SDV oraz instrukcje wdrażania usługi i klienta, którymi może sterować użytkownik.
Omówienie architektury
System preferencji użytkownika oddziela przechowywanie ustawień użytkownika i zarządzanie nimi od egzekwowania i stosowania tych ustawień. Najważniejsze terminy architektoniczne zostały podsumowane w tabeli:
| Funkcja | Opis | Rola | Odpowiedzialność |
|---|---|---|---|
| Usługa, którą użytkownik może kontrolować | Standardowy pakiet usług SDV, który zarządza konkretną domeną (np. klimatyzacją, fotelami samochodowymi, dźwiękiem). | Określa zamierzony stan możliwości i ograniczeń sprzętu lub podsystemu, którym steruje. |
|
| Agent preferencji użytkownika | Centralny aranżer | Zapewnia scentralizowane przechowywanie danych, zarządzanie profilami użytkowników i centrum powiadomień. |
|
| Klient ustawień użytkownika | Aplikacja lub usługa, która udostępnia interfejs użytkownika, np. aplikacja IVI z interfejsem HMI lub inną logiką, która musi wchodzić w interakcję z ustawieniami użytkownika. | Wchodzi w interakcje z użytkownikami, wyświetla ustawienia i inicjuje prośby o zmiany. |
|
Wdrażanie usługi, którą użytkownik może kontrolować
Aby poinformować agenta User Preferences o tym, jakie klucze istnieją, jakie są ich typy danych, wartości domyślne i ograniczenia (np. wartości minimalne i maksymalne), pakiet usług musi wchodzić w interakcję z interfejsem UserPreferencesRegistryService. Po uruchomieniu usługa musi zarejestrować udostępniane przez nią ustawienia. Agent wywołuje usługę, gdy użytkownik próbuje zmodyfikować ustawienie (lub gdy przełącza się między użytkownikami).RequestSettingsChange
Aby umożliwić ustawieniom użytkownika (i użytkownikowi z HMI) sterowanie usługą, wykonaj czynności opisane w tej sekcji.
Zdefiniuj interfejsy usług w pliku VSIDL:
- Jako serwer zaimplementuj
com.sdv.google.user_preferences.user_controllable.UserControllableService. Umożliwia to agentowi wysyłanie do Ciebie próśb o zmianę. - Klient: konsumuje
com.sdv.google.user_preferences.UserPreferencesRegistryService. Spowoduje to zarejestrowanie ustawień podczas uruchamiania.
Poniższy przykład pokazuje
service_bundle.vsidlw przypadku usługi, którą może kontrolować użytkownik:service_bundle { name: "MyFeatureService" server { service: "com.sdv.google.user_preferences.user_controllable.UserControllableService" } client { service: "com.sdv.google.user_preferences.UserPreferencesRegistryService" } }- Jako serwer zaimplementuj
Zarejestruj ustawienia przy uruchamianiu w protokole klucza:
user_preferences_registry_service.proto- Połącz z
UserPreferencesRegistryService. - Utwórz instancję
RegisterSettingsRequest. - Zdefiniuj instancję
SettingsGroup(logiczną kolekcję ustawień). W przypadku każdego ustawienia określ:
* **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).Zadzwoń:
RegisterSettings()
Poniższy przykład jest napisany w języku 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?;- Połącz z
Obsługuj prośby o zmianę ustawień w
user_controllable_service.proto:- Zaimplementuj RPC
RequestSettingsChange. - Sprawdź, czy żądane wartości są prawidłowe w bieżącym kontekście (np. czy sprzęt jest gotowy).
- Zastosuj konkretną logikę, aby wprowadzić zmianę (np. przesuń fotel, zmień szybkość wentylatora).
- Zwróć zastosowane wartości w
RequestSettingsChangeResponse: - Jeśli zaakceptujesz zmianę, zwróć nową wartość.
- Jeśli wartość została odrzucona lub ograniczona, zwróć ustawioną (lub zachowaną) wartość.
Poniższy przykład jest napisany w języku 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, }) }- Zaimplementuj RPC
Zaimplementuj
FactoryResetRPC, aby przywrócić podsystem do czystego stanu:- Zresetuj wszystkie ustawienia do wartości domyślnych (zdefiniowanych w konfiguracji kodu).
- Wywołaj funkcję
UpdateSettingsw usłudze rejestru, aby poinformować agenta, że wartości zostały zmienione zewnętrznie (przez zresetowanie, a nie przez żądanie użytkownika).
Zgodność wsteczna i ewolucja schematu
Schemat ustawień zdefiniowanych przez usługę, którą użytkownik może kontrolować, jest uważany za interfejs publiczny. Ten interfejs jest używany przez agenta preferencji użytkownika i różnych klientów (np. HMI), którzy mogą mieć różne harmonogramy wydań. Dlatego zachowanie ścisłej zgodności wstecznej jest niezbędne, aby zapobiec niestabilności systemu i awariom klientów.
Zgodne zmiany
Usługa, którą użytkownik może kontrolować, powinna wprowadzać do schematu ustawień tylko zgodne zmiany. Te zmiany zapewniają, że starsze klienty nadal działają prawidłowo bez aktualizacji:
Dodaj nowe ustawienie opcjonalne z rozsądną wartością domyślną:
- To ustawienie jest opcjonalne, więc klienci muszą sprawdzać jego obecność, aby obsługiwać różne wersje usługi.
- Klienci ignorują to ustawienie, jeśli nie zostali zaktualizowani, aby je rozpoznawać.
- Jeśli nie ma preferencji użytkownika dotyczących nowego ustawienia, agent używa podanej wartości domyślnej.
Niezgodne zmiany
Te zmiany są uznawane za powodujące niezgodność i są zabronione, ponieważ natychmiast powodują problemy u starszych klientów:
Usuń istniejące ustawienie.
Zmień znaczenie, jednostkę lub typ danych istniejącego ustawienia (np. zmień liczbę całkowitą reprezentującą temperaturę w stopniach Celsjusza na liczbę zmiennoprzecinkową reprezentującą ciśnienie w paskalach).
Zmień wartość domyślną istniejącego ustawienia. Głównym powodem zakazu zmiany wartości domyślnej jest potencjalne skomplikowanie migracji zapisanych danych. Agent przechowuje ustawienia na podstawie schematu rejestracji usługi. Zmiana wartości domyślnej wymagałaby skomplikowanej logiki, aby przenieść wszystkie istniejące profile użytkowników na nową wartość domyślną, lub wiązałaby się z ryzykiem używania technicznie nieprawidłowej wartości w przypadku użytkowników, którzy nigdy nie ustawili wyraźnie preferencji.
Obsługa wymaganych zmian powodujących niezgodność
Jeśli wymagana jest zmiana powodująca niezgodność, usługa nie może modyfikować istniejącego ustawienia. Podejście do zarządzania tą zmianą przy zachowaniu zgodności znajduje się głównie w logice usługi kontrolowanej przez użytkownika:
- Utwórz nowe ustawienie z odpowiednią nową definicją, kluczem lub jednostką.
- Wycofaj dotychczasowe ustawienie, rejestrując je pod kątem zgodności. Używaj tego nowego ustawienia podczas pozyskiwania nowych klientów.
- Doradzaj nowym klientom, aby w logice biznesowej usługi, którą użytkownik może kontrolować, wdrażali logikę zgodności.
Implementacja RequestSettingsChange usługi musi zapewniać, że zmiana jednego ustawienia jest odzwierciedlana w jego wycofanym odpowiedniku, a zmiana odpowiednika jest odzwierciedlana w ustawieniu.
Przykładowa logika zgodności:
Usługa wycofuje stare ustawienie
TEMPERATURE_C(stopnie Celsjusza) i wprowadzaTEMPERATURE_K(stopnie Kelwina).Gdy agent wyśle prośbę o zaktualizowanie ustawienia new:
Usługa otrzymuje żądanie
TEMPERATURE_K(np.295, 15 K).Logika usługi przekształca tę wartość na stopnie Celsjusza (22°C) i wewnętrznie aktualizuje oraz zapisuje obie wartości, aby zachować spójność w przypadku starszych klientów.
Gdy pracownik obsługi wyśle z klienta starszego typu prośbę o ustawienie, które zostało wycofane:
- Usługa otrzymuje żądanie dotyczące
TEMPERATURE_C(np. 24°C). - Usługa przekształca tę wartość na stopnie Kelwina (297,15 K) i wewnętrznie aktualizuje oraz zapisuje obie wartości.
- Usługa otrzymuje żądanie dotyczące
Ta strategia podwójnego zapisu zapewnia, że wszyscy klienci, niezależnie od harmonogramu wydania, odczytują spójne i dokładne dane, co pozwala uniknąć problemów spowodowanych niezgodnością wydań zewnętrznych.
Implementowanie klienta
Aby zaimplementować klienta (np. HMI), który wchodzi w interakcję z agentem UserPreferences:
Zdefiniuj pakiet usług klienta, aby wchodzić w interakcje z określonymi interfejsami User Preferences. W pliku VSIDL:
- Jako serwer zaimplementuj interfejs
com.sdv.google.user_preferences.view.ChangeNotifier, aby umożliwić agentowi wysyłanie do klienta powiadomień w czasie rzeczywistym o zmianach ustawień. - Jako klient używaj
com.sdv.google.user_preferences.UserPreferencesManagementService, aby prosić o zmiany ustawień i subskrybować aktualizacje. - Jako klient możesz używać
com.sdv.google.user_preferences.UserPreferencesAdminServicedo zarządzania profilami użytkowników (np. tworzenia, wybierania, usuwania i przywracania do ustawień fabrycznych).
Poniższy przykład przedstawia plik
service_bundle.vsidlklienta: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" } }Dodaj odpowiednie zasady autoryzacji. Aby zezwolić na komunikację agenta preferencji użytkownika, utwórz klienta dla określonych serwerów. Przykład:
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 }- Jako serwer zaimplementuj interfejs
Ustawienia prośby możesz zmienić w
UserPreferencesManagementService:- Połącz z
UserPreferencesManagementService. - Utwórz instancję
RequestSettingsChangeRequest, określającSettingsGroupIdi odpowiednie ustawienia. - Opcjonalnie. Ustaw wartość
ChangePersistencePolicynaPERSISTENT_CHANGE(domyślnie) lubNON_PERSISTENT_CHANGE. - Zadzwoń:
RequestSettingsChange()
Poniższy przykład jest napisany w języku 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?;- Połącz z
Subskrybuj zmiany ustawień za pomocą symboli
user_preferences_management_service.protoichange_notifier.proto:- Zaimplementuj wywołanie
OnSettingsChangeRPC z interfejsuChangeNotifierw pakiecie usług. Agent User Preferences wywołuje tę metodę, gdy nastąpi zmiana. - Połącz z
UserPreferencesManagementService. - Utwórz
SubscribeToSettingsChangeAndGetSettingsRequest, w którym określiszSettingsGroupId, które chcesz monitorować. - Aby przywrócić stan ustawień, a następnie wysyłać przyszłe aktualizacje za pomocą implementacji
OnSettingsChange, wywołaj funkcjęSubscribeToSettingsChangeAndGetSettings().
Poniższy przykład implementacji interfejsu
ChangeNotifierjest napisany w języku 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()) } }Poniższy przykład subskrypcji jest napisany w języku 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?;- Zaimplementuj wywołanie
Opcjonalnie: klienci, którzy zarządzają profilami użytkowników (np. dedykowana aplikacja do ustawień), mogą używać
user_preferences_admin_service.protodo interakcji z usługą administracyjną:- Połącz z
UserPreferencesAdminService. - Do zarządzania profilami użytkowników używaj wywołań RPC, takich jak
CreateUser,SelectUser,DeleteUser,FactoryResetiListUsers.
Ten przykład tworzenia użytkownika jest napisany w języku Rust:
admin_service_client.CreateUser(&CreateUserRequest { user: Some(User { id: 1, flags: UserFlags::DRIVER.value(), ..Default::default() }).into(), ..Default::default() }).await?;- Połącz z
Odkrywaj dostępne ustawienia i użytkowników za pomocą usługi administracyjnej z
user_preferences_admin_service.proto:- Aby uzyskać listę wszystkich zarejestrowanych użytkowników, wywołaj
ListUsers()naUserPreferencesAdminService. - Pobierz ustawienia konkretnego użytkownika, wywołując funkcję
GetUserSettings(user_id)wUserPreferencesAdminService.
Poniższy przykład wyświetlania użytkowników i pobierania ustawień jest napisany w języku 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); }- Aby uzyskać listę wszystkich zarejestrowanych użytkowników, wywołaj
Podsumowanie procesu
- Klient uruchamia usługi zarządzania i administracyjne agenta i się z nimi łączy.
- Klient wykrywa grupy ustawień i subskrybuje je, a następnie wyświetla stan początkowy.
- Klient wysyła do agenta
RequestSettingsChange. - Agent wysyła do klienta powiadomienie
OnSettingsChangeze zaktualizowanymi ustawieniami i stanem.
Pełny przykład znajdziesz w HMIService@samples/user_preferences/v1/.
Wdrażanie agenta
Pakiet usług uruchomiony przez orkiestratora implementuje agenta preferencji użytkownika. Dla wygody użytkowników udostępniamy implementację referencyjną.
Dodatkowo udostępniamy ten przykładowy pakiet usług user_preferences_sample.vsidl dla agenta:
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"
}
}
Implementacja może korzystać z wygenerowanego oprogramowania pośredniczącego i powinna być kompilowana do pliku rust_ffi_shared, do którego odwołuje się pakiet usług implementujący agenta preferencji użytkownika:
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"
}
Plik zasad autoryzacji.textproto klienta wymaga niezbędnych uprawnień:
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
}
Dodatek: kluczowe pojęcia
W tej sekcji opisujemy kilka kluczowych pojęć.
Użytkownicy
Każdy użytkownik pojazdu (User klasa) może utworzyć konto, na którym będzie przechowywać swoje preferencje. Ustawienia użytkownika obsługują konta tylko w przypadku kierowców pojazdów.
Kierowcy goście mogą tworzyć konta tymczasowe, które są automatycznie usuwane po jednorazowym użyciu.
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;
}
Grupy ustawień
Grupy ustawień (klasa SettingsGroup) służą do porządkowania powiązanych ustawień i zarządzania nimi.
Każdy konfigurowalny komponent w pojeździe definiuje swoje ustawienia jako grupy, które składają się z listy ustawień przedstawionych jako pary klucz-wartość. Na przykład
pojazdy z elektrycznie sterowanymi fotelami mogą zdefiniować grupę ustawień dla fotela kierowcy
i inną dla fotela pasażera z przodu, przy czym obie grupy zawierają ustawienia o identycznych nazwach.
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;
}
Ustawienia
Ustawienie, komunikat (klasa Setting), reprezentuje pojedyncze ustawienie w SettingsGroup. Ten komunikat składa się z klucza, czyli nazwy ustawienia, oraz wartości, która może być jednego z kilku typów.
Ten przykład pokazuje, jak ustawienie jest reprezentowane za pomocą klucza i wartości:
// 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;
}
}
Definicje ustawień
Definicje ustawień (SettingDefinition class) służą jako szablony poszczególnych ustawień w grupie ustawień. Określają one podstawowe cechy ustawienia, np. czy jest ono udostępniane wszystkim użytkownikom, czy jest specyficzne dla każdego użytkownika, czy też jest zarządzane zewnętrznie (przekazywane).
Definicje ustawień określają też wszelkie ograniczenia dotyczące wartości ustawienia, takie jak wartości minimalne i maksymalne, dopuszczalne przyrosty lub ograniczony zestaw opcji. Te ograniczenia zapewniają integralność i spójność danych w przypadku każdego ustawienia.
// 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;
}
Przykładowa sekwencja zdarzeń, gdy użytkownik aktualizuje ustawienie
Ten przykład ilustruje kolejność zdarzeń, które występują, gdy użytkownik używa systemu operacyjnego Android Automotive (AAOS) w samochodowym systemie multimedialnym (IVI) do aktualizowania ustawienia:
Rysunek 1. Wydarzenia, gdy użytkownik zmieni ustawienie.
Przykładowa sekwencja zdarzeń podczas uruchamiania pojazdu
Ten przykład pokazuje, jak preferencje użytkownika SDV stosują preferowane ustawienia użytkownika podczas uruchamiania pojazdu:
Rysunek 2. Zdarzenia, gdy pojazd jest uruchamiany.