本頁面提供 SDV 使用者偏好設定系統的架構指南,以及實作使用者可控管服務和用戶端的說明。
架構總覽
使用者偏好設定系統會將使用者設定的儲存和管理作業,與這些設定的強制執行和套用作業分開。下表匯總了主要架構用語:
| 功能 | 說明 | 角色 | 責任 |
|---|---|---|---|
| 使用者可控服務 | 管理特定網域的標準 SDV 服務套件 (例如 HVAC、汽車座椅、音訊) | 提供所控管硬體或子系統的功能和限制的預期狀態。 |
|
| 使用者偏好設定代理程式 | 中央自動化調度管理工具 | 提供集中式儲存空間、使用者設定檔管理和通知中心。 |
|
| 使用者偏好設定用戶端 | 提供使用者介面的應用程式或服務,例如具有 HMI 或其他邏輯的 IVI 應用程式,需要與使用者偏好設定互動 | 與使用者互動、顯示設定,以及發起變更要求。 |
|
實作使用者可控管的服務
如要讓 User Preferences 代理程式瞭解現有的鍵、資料類型、預設值和限制 (例如最小值和最大值),服務套件必須與 UserPreferencesRegistryService 介面互動。服務啟動時,必須註冊公開的設定。當使用者嘗試修改設定 (或切換使用者) 時,代理程式會呼叫服務中的 RequestSettingsChange。
請按照本節的步驟操作,允許使用者偏好設定 (以及具有 HMI 的使用者) 控制您的服務。
在 VSIDL 檔案中定義服務介面:
- 做為伺服器,實作
com.sdv.google.user_preferences.user_controllable.UserControllableService。 這樣一來,服務專員就能傳送變更要求。 - 身為用戶端,請使用
com.sdv.google.user_preferences.UserPreferencesRegistryService。這會在啟動時註冊設定。
以下範例顯示使用者可控服務的
service_bundle.vsidl:service_bundle { name: "MyFeatureService" server { service: "com.sdv.google.user_preferences.user_controllable.UserControllableService" } client { service: "com.sdv.google.user_preferences.UserPreferencesRegistryService" } }- 做為伺服器,實作
在啟動時,於主要原型中註冊設定:
user_preferences_registry_service.proto- 連線至「
UserPreferencesRegistryService」。 - 建立
RegisterSettingsRequest的執行個體。 - 定義
SettingsGroup的例項 (設定的邏輯集合)。 為每項設定定義下列項目:
* **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).呼叫
RegisterSettings()。
以下範例是以概念性 Rust 語言編寫:
let temperature_setting = SettingDefinition { name: "TEMPERATURE".to_string(), kind: SettingKind::PER_USER.into(), default_value: Value::Int64(22), // Default 22 degrees constraint: Some(Constraints::Int64Constraints(Int64Constraints { min_value: Some(16), max_value: Some(32), ..Default::default() })), ..Default::default() }; registry_client.RegisterSettings(&RegisterSettingsRequest { group_name: "HVAC".to_string(), version: "1.0".to_string(), settings_definitions: vec![temperature_setting], }).await?;- 連線至「
在
user_controllable_service.proto中處理設定變更要求:- 實作
RequestSettingsChangeRPC。 - 驗證要求的值在目前環境中是否有效 (例如硬體是否已準備就緒)。
- 套用特定邏輯來套用變更 (例如移動座椅、變更風扇速度)。
- 在
RequestSettingsChangeResponse中傳回套用的值: - 如果接受變更,請傳回新值。
- 如果您拒絕或限制值,請傳回您設定 (或保留) 的值。
以下範例是以概念性 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, }) }- 實作
實作
FactoryResetRPC,將子系統還原為乾淨狀態:- 將所有設定重設為預設值 (如程式碼設定中所定義)。
- 在登錄服務上呼叫
UpdateSettings,通知代理程式值已從外部變更 (透過重設,而非使用者要求)。
回溯相容性和結構定義演進
使用者可控管服務定義的設定結構定義,視為公開介面。使用者偏好設定代理程式和各種用戶端 (例如 HMI) 會使用這個介面,但這些用戶端的發布時間表可能不同。因此,維持嚴格的回溯相容性至關重要,可避免系統不穩定和用戶端中斷。
相容的變更
使用者可控服務應只對設定結構定義進行相容的變更。這些變更可確保舊版用戶端在不更新的情況下,仍能正常運作:
新增具有合理預設值的新選用設定:
- 這項設定為選用設定,因此用戶端必須檢查是否存在這項設定,才能支援不同版本的服務。
- 如果用戶端尚未更新,無法辨識這項設定,就會忽略這項設定。
- 如果沒有新設定的使用者偏好設定,代理程式會使用提供的預設值。
不相容的變更
以下變更視為破壞性變更,且禁止使用,因為這些變更會立即影響舊版用戶端:
移除現有設定。
變更現有設定的意義、單位或資料類型 (例如,從代表攝氏溫度的整數變更為代表帕斯卡壓力的浮點數)。
變更現有設定的預設值。禁止變更預設值的主要原因是,這可能會導致持續性資料遷移作業變得複雜。代理程式會根據服務的註冊結構定義儲存設定。如要變更預設值,必須使用複雜的邏輯將所有現有使用者設定檔遷移至新的預設值,否則對於從未明確設定偏好的使用者,可能會使用技術上不正確的值。
處理必要的破壞性變更
如果需要破壞性變更,服務不得修改現有設定。如要管理這項轉換作業並維持相容性,主要方法是透過使用者可控的服務邏輯:
- 使用所需的新定義、鍵或單位建立新設定。
- 註冊現有設定以確保相容性,藉此淘汰該設定。開發新用戶端時,請使用這項新設定。
- 建議新客戶在使用者可控服務的商業邏輯中導入相容性邏輯。
服務的 RequestSettingsChange 實作項目必須確保一項設定的變更會反映在已淘汰的對應項目中,而對應項目的變更也會反映在設定中。
相容性邏輯範例:
這項服務會淘汰舊設定
TEMPERATURE_C(攝氏),並推出TEMPERATURE_K(絕對溫度)。代理商傳送要求來更新新設定時:
服務收到
TEMPERATURE_K的要求 (例如 295.15 K)。服務的邏輯會將這個值轉換為攝氏 (22°C),並在內部更新及保存這兩個值,以維持舊版用戶端的一致性。
如果代理程式從舊版用戶端傳送已淘汰設定的要求:
- 服務收到
TEMPERATURE_C的要求 (例如 24°C)。 - 這項服務會將此值轉換為絕對溫度 (297.15 K),並在內部更新及保存這兩個值。
- 服務收到
這項雙重寫入策略可確保所有用戶端 (無論發布時間表為何) 都能讀取一致且準確的資料,避免因外部發布不符而導致中斷。
實作用戶端
如要實作與 User Preferences 代理互動的用戶端 (例如 HMI):
定義用戶端服務套件,與特定使用者偏好設定介面互動。在 VSIDL 檔案中:
- 以伺服器身分實作
com.sdv.google.user_preferences.view.ChangeNotifier,讓代理程式即時將設定變更通知傳送給用戶端。 - 身為用戶端,請使用
com.sdv.google.user_preferences.UserPreferencesManagementService要求修改設定及訂閱更新。 - 用戶端可以使用
com.sdv.google.user_preferences.UserPreferencesAdminService管理使用者設定檔 (例如建立、選取、刪除、恢復原廠設定)。
以下範例顯示用戶端的
service_bundle.vsidl檔案:service_bundle { name: "MyHmiClient" server { service: "com.sdv.google.user_preferences.view.ChangeNotifier" } client { service: "com.sdv.google.user_preferences.UserPreferencesManagementService" } client { service: "com.sdv.google.user_preferences.UserPreferencesAdminService" } }據此新增授權政策。如要允許使用者偏好設定代理程式進行通訊,請為指定伺服器建立用戶端。例如:
server { service: "com.sdv.google.user_preferences.view.ChangeNotifier" allow_all_channels: true } client { service: "com.sdv.google.user_preferences.UserPreferencesManagementService" allow_all_channels: true } client { service: "com.sdv.google.user_preferences.UserPreferencesAdminService" allow_all_channels: true }- 以伺服器身分實作
如要變更要求設定,請按照下列步驟操作:
UserPreferencesManagementService- 連線至「
UserPreferencesManagementService」。 - 建立
RequestSettingsChangeRequest的執行個體,並指定SettingsGroupId和所需設定。 - 選用。將
ChangePersistencePolicy設為PERSISTENT_CHANGE(預設) 或NON_PERSISTENT_CHANGE。 - 呼叫
RequestSettingsChange()。
以下範例是以概念性 Rust 語言編寫:
management_client.RequestSettingsChange(&RequestSettingsChangeRequest { settings_group_id: Some(SettingsGroupId { service_fqin: hvac_service_fqin.to_string(), name: "HVAC".to_string(), ..Default::default() }).into(), settings: vec![Setting { key: "TEMPERATURE".to_string(), value: Some(Value::Int64(24)), ..Default::default() }], change_persistence_policy: ChangePersistencePolicy::PERSISTENT_CHANGE.into(), ..Default::default() }).await?;- 連線至「
使用
user_preferences_management_service.proto和change_notifier.proto訂閱設定變更:- 在服務套件中,從
ChangeNotifier介面實作OnSettingsChangeRPC。發生變更時,使用者偏好設定代理程式會叫用這個方法。 - 連線至「
UserPreferencesManagementService」。 - 建立
SubscribeToSettingsChangeAndGetSettingsRequest,指定要監控的SettingsGroupId。 - 如要傳回設定的狀態,然後透過
OnSettingsChange實作傳送日後的更新,請呼叫SubscribeToSettingsChangeAndGetSettings()。
以下是實作
ChangeNotifier介面的範例,以概念性 Rust 語言編寫:#[async_trait] impl ChangeNotifier for MyHmiServiceImpl { async fn OnSettingsChange( &self, _caller_id: ServiceFqin, request: &OnSettingsChangeRequest, ) -> SdvResult<OnSettingsChangeResponse> { // Process the active_settings, pending_changes, and persisted_settings // Update your UI or internal state accordingly. info!("Received settings change for group: {}", request.settings_group_id.name); // ... Ok(OnSettingsChangeResponse::new()) } }以下是概念性 Rust 的訂閱範例:
management_client.SubscribeToSettingsChangeAndGetSettings(&SubscribeToSettingsChangeAndGetSettingsRequest { settings_group_id: Some(SettingsGroupId { service_fqin: hvac_service_fqin.to_string(), name: "HVAC".to_string(), ..Default::default() }).into(), ..Default::default() }).await?;- 在服務套件中,從
選用:管理使用者設定檔的用戶端 (例如專屬設定應用程式) 可以使用
user_preferences_admin_service.proto與管理服務互動:- 連線至「
UserPreferencesAdminService」。 - 使用
CreateUser、SelectUser、DeleteUser、FactoryReset和ListUsers等 RPC 管理使用者設定檔。
以下是建立使用者的概念性 Rust 範例:
admin_service_client.CreateUser(&CreateUserRequest { user: Some(User { id: 1, flags: UserFlags::DRIVER.value(), ..Default::default() }).into(), ..Default::default() }).await?;- 連線至「
透過管理服務探索可用的設定和使用者:
user_preferences_admin_service.proto- 呼叫
UserPreferencesAdminService上的ListUsers(),取得所有已註冊使用者的清單。 - 呼叫
UserPreferencesAdminService上的GetUserSettings(user_id),取得特定使用者的設定。
以下範例說明如何列出使用者並取得設定,概念上是以 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); }- 呼叫
流程摘要
- 用戶端會啟動並連線至代理程式的管理和管理服務。
- 用戶端會探索並訂閱設定群組,並顯示初始狀態。
- 用戶端會將
RequestSettingsChange傳送給代理程式。 - 服務專員會向用戶端傳送
OnSettingsChange通知,其中包含更新的設定和狀態。
如需完整的工作範例,請參閱 HMIService 中的 @samples/user_preferences/v1/。
實作代理程式
由協調器啟動的服務套件會實作使用者偏好設定代理程式。為方便起見,我們提供參考實作。
此外,代理程式還提供這個範例服務套裝組合 user_preferences_sample.vsidl 檔案:
package: "com.sdv.oem.user_preferences"
service_bundle {
name: "UserPreferencesServiceBundle"
server {
service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
}
server {
service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
}
server {
service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
}
client {
service: "com.sdv.google.user_preferences.view.ChangeNotifier"
}
client {
service: "com.sdv.google.user_preferences.user_controllable.UserControllableService"
}
}
實作作業可以使用產生的中介軟體,並應編譯成服務套件參照的 rust_ffi_shared,該服務套件會實作使用者偏好設定代理程式:
sdv_service_bundle_metadata {
# This name must match the agent's Bundle Name
name: "UserPreferencesServiceBundle"
version_number: 1
version_name: "1"
native_library_path: "lib64/<USER-PREFERENCES-FFI-LIB>.so"
orchestration_config_path: "etc/user_preferences_service_bundle/<USER-PREFERENCES-ORCHESTRATION>.textproto"
authorization_policy_path: "etc/user_preferences_service_bundle/permissions.textproto"
}
用戶端的授權政策.textproto檔案必須具備必要權限:
client {
service: "com.sdv.google.user_preferences.UserPreferencesManagementService"
allow_all_channels: true
}
client {
service: "com.sdv.google.user_preferences.UserPreferencesRegistryService"
allow_all_channels: true
}
client {
service: "com.sdv.google.user_preferences.UserPreferencesAdminService"
allow_all_channels: true
}
附錄:重要概念
本節說明幾個重要概念。
使用者
每位車輛使用者 (User 類別) 都可以建立帳戶,儲存個人偏好設定。使用者偏好設定僅支援車輛駕駛人的帳戶。
訪客駕駛可以建立臨時帳戶,系統會在一次使用後自動刪除。
message User {
// Required.
// A unique ID for the user.
int32 id = 1;
// Bit flags that define properties of user. Integer values have to be powers of 2 as they are used as
// a bit mask.
enum UserFlags {
// Due to Protobuff requirement to have the first enum option set to 0,
// Assign 0 to an unused flag
UNSET = 0x0;
// Marks the user as vehicle driver
DRIVER = 0x01;
// Ephemeral users have non-persistent state, once another user is selected
// the profile is deleted automatically
EPHEMERAL = 0x02;
}
// Required.
// Bitmask for the user flags defined above
int32 flags = 2;
}
設定群組
設定群組 (SettingsGroup 類別) 可整理及管理相關設定。
車輛中的每個可設定元件都會將設定定義為群組,其中包含以鍵/值組合表示的設定清單。舉例來說,配備電動座椅的車輛可以為駕駛座定義一組設定,並為前座乘客定義另一組設定,兩組設定都包含名稱相同的設定。
message SettingsGroup {
// Required.
// The identifier for this group.
SettingsGroupId id = 1;
// Required.
// The version number of schema used by this setting group.
string version = 2;
// Required.
// The list of settings within the setting group.
repeated Setting settings = 3;
}
設定
設定 (Setting 類別) 代表 SettingsGroup 中的單一設定。這則訊息包含鍵 (設定名稱) 和值 (可以是多種型別)。
這個範例顯示如何以鍵和值表示設定:
// A key value pair representing a setting within a UserControllableService SettingsGroup.
message Setting {
// Required.
// A name that uniquely identifies a setting within a SettingsGroup.
string key = 1;
// Required.
// New value of the setting.
oneof value {
bool bool = 2;
float float = 3;
int32 int32 = 4;
int64 int64 = 5;
bytes blob = 6;
int32 enum = 7;
}
}
設定定義
設定定義 (SettingDefinition 類別) 可做為設定群組中個別設定的範本。這些屬性會指定設定的基本特徵,例如設定是否會與所有使用者共用、是否專屬於每位使用者,或是由外部管理 (傳遞)。
重要事項:設定定義也會定義設定值的任何限制,例如最小值和最大值、允許的增量或受限的選項集。這些限制可確保每個設定的資料完整性和一致性。
// The definition of a setting, along with its properties such as type and constraints
message SettingDefinition {
// Required.
SettingKind kind = 1;
// Required.
SettingWithConstraints setting_with_constraints = 2;
}
enum SettingKind {
// A setting which is applied for all vehicle users.
SHARED = 0;
// Store the value of the setting for each vehicle user separately.
PER_USER = 1;
// UserControllableService is fully responsible for the storage of PASSTHROUGH setting.
// User Preferences only notifies UserControllableService when the value is explicitly set by
// user. User Preferences does not attempt to request changes based on user change or service
// registration.
PASSTHROUGH = 2;
};
message SettingWithConstraints {
// Required
Setting setting = 1;
// Required.
// Defines restrictions on the setting's value
oneof constraints {
FloatConstraints float_constraints = 2;
Int32Constraints int32_constraints = 3;
Int64Constraints int64_constraints = 4;
EnumConstraints enum_constraints = 5;
}
}
message Int32Constraints {
// The minimum value that a setting can have.
optional int32 min_value = 1;
// The maximum value that a setting can have.
optional int32 max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional int32 step = 3;
}
message Int64Constraints {
// The minimum value that a setting can have.
optional int64 min_value = 1;
// The maximum value that a setting can have.
optional int64 max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional int64 step = 3;
}
message FloatConstraints {
// The minimum value that a setting can have.
optional float min_value = 1;
// The maximum value that a setting can have.
optional float max_value = 2;
// The step by which a setting's value can be increased or decreased.
optional float step = 3;
}
message EnumConstraints {
// Required.
// List of unique values sorted in ascending order.
repeated int32 possible_values = 1;
}
使用者更新設定時的事件順序範例
以下範例說明使用者透過 Android Automotive OS (AAOS) 車用資訊娛樂系統 (IVI) 更新設定時,會發生哪些事件:
圖 1. 使用者變更設定時的事件。
車輛啟動時的事件順序範例
這個範例說明 SDV 使用者偏好設定如何在車輛啟動時套用使用者偏好的設定:
圖 2. 車輛發動時的事件。