使用者偏好設定架構和實作指南

本頁面提供 SDV 使用者偏好設定系統的架構指南,以及實作使用者可控管服務和用戶端的說明。

架構總覽

使用者偏好設定系統會將使用者設定的儲存和管理作業,與這些設定的強制執行和套用作業分開。下表匯總了主要架構用語:

功能說明角色責任
使用者可控服務 管理特定網域的標準 SDV 服務套件 (例如 HVAC、汽車座椅、音訊) 提供所控管硬體或子系統的功能和限制的預期狀態。
  • 註冊:向使用者偏好設定代理程式說明支援的設定 (中繼資料、預設值、限制)。
  • 強制執行和自主性:接收變更設定的要求、根據目前狀態和安全規則驗證要求,並將要求套用至硬體。維持充分自主權,並指定是否接受或拒絕設定變更。
使用者偏好設定代理程式 中央自動化調度管理工具 提供集中式儲存空間、使用者設定檔管理和通知中心。
  • 儲存空間:為每位使用者 (例如駕駛人、乘客) 保留設定。
  • 路徑:將來自用戶端 (例如人機介面 (HMI)) 的要求,變更為適當的使用者可控服務。
  • 通知:使用 ChangeNotifier 介面,向感興趣的對象 (檢視畫面或 HMI) 廣播變更。
  • 使用者管理:處理使用者切換作業,並將正確的持續性狀態套用至所有已註冊的服務。
使用者偏好設定用戶端 提供使用者介面的應用程式或服務,例如具有 HMI 或其他邏輯的 IVI 應用程式,需要與使用者偏好設定互動 與使用者互動、顯示設定,以及發起變更要求。
  • 顯示:向使用者呈現目前的設定和限制。
  • 要求變更:將使用者發起的設定修改要求傳送至使用者偏好設定代理程式。
  • 接收通知:訂閱並回應設定的即時更新。

實作使用者可控管的服務

如要讓 User Preferences 代理程式瞭解現有的鍵、資料類型、預設值和限制 (例如最小值和最大值),服務套件必須與 UserPreferencesRegistryService 介面互動。服務啟動時,必須註冊公開的設定。當使用者嘗試修改設定 (或切換使用者) 時,代理程式會呼叫服務中的 RequestSettingsChange

請按照本節的步驟操作,允許使用者偏好設定 (以及具有 HMI 的使用者) 控制您的服務。

  1. 在 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"
        }
    }
    
  2. 在啟動時,於主要原型中註冊設定:user_preferences_registry_service.proto

    1. 連線至「UserPreferencesRegistryService」。
    2. 建立 RegisterSettingsRequest 的執行個體。
    3. 定義 SettingsGroup 的例項 (設定的邏輯集合)。
    4. 為每項設定定義下列項目:

      *   **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. 呼叫 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?;
    
  3. user_controllable_service.proto 中處理設定變更要求:

    1. 實作 RequestSettingsChange RPC。
    2. 驗證要求的值在目前環境中是否有效 (例如硬體是否已準備就緒)。
    3. 套用特定邏輯來套用變更 (例如移動座椅、變更風扇速度)。
    4. RequestSettingsChangeResponse 中傳回套用的值:
    5. 如果接受變更,請傳回新值。
    6. 如果您拒絕或限制值,請傳回您設定 (或保留) 的值。

    以下範例是以概念性 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,
        })
    }
    
  4. 實作 FactoryReset RPC,將子系統還原為乾淨狀態:

    1. 將所有設定重設為預設值 (如程式碼設定中所定義)。
    2. 在登錄服務上呼叫 UpdateSettings,通知代理程式值已從外部變更 (透過重設,而非使用者要求)。

回溯相容性和結構定義演進

使用者可控管服務定義的設定結構定義,視為公開介面。使用者偏好設定代理程式和各種用戶端 (例如 HMI) 會使用這個介面,但這些用戶端的發布時間表可能不同。因此,維持嚴格的回溯相容性至關重要,可避免系統不穩定和用戶端中斷。

相容的變更

使用者可控服務應只對設定結構定義進行相容的變更。這些變更可確保舊版用戶端在不更新的情況下,仍能正常運作:

  • 新增具有合理預設值的新選用設定:

    • 這項設定為選用設定,因此用戶端必須檢查是否存在這項設定,才能支援不同版本的服務。
    • 如果用戶端尚未更新,無法辨識這項設定,就會忽略這項設定。
    • 如果沒有新設定的使用者偏好設定,代理程式會使用提供的預設值。

不相容的變更

以下變更視為破壞性變更,且禁止使用,因為這些變更會立即影響舊版用戶端:

  • 移除現有設定。

  • 變更現有設定的意義、單位或資料類型 (例如,從代表攝氏溫度的整數變更為代表帕斯卡壓力的浮點數)。

  • 變更現有設定的預設值。禁止變更預設值的主要原因是,這可能會導致持續性資料遷移作業變得複雜。代理程式會根據服務的註冊結構定義儲存設定。如要變更預設值,必須使用複雜的邏輯將所有現有使用者設定檔遷移至新的預設值,否則對於從未明確設定偏好的使用者,可能會使用技術上不正確的值。

處理必要的破壞性變更

如果需要破壞性變更,服務不得修改現有設定。如要管理這項轉換作業並維持相容性,主要方法是透過使用者可控的服務邏輯:

  1. 使用所需的新定義、鍵或單位建立新設定。
  2. 註冊現有設定以確保相容性,藉此淘汰該設定。開發新用戶端時,請使用這項新設定。
  3. 建議新客戶在使用者可控服務的商業邏輯中導入相容性邏輯。

服務的 RequestSettingsChange 實作項目必須確保一項設定的變更會反映在已淘汰的對應項目中,而對應項目的變更也會反映在設定中。

相容性邏輯範例:

  1. 這項服務會淘汰舊設定 TEMPERATURE_C (攝氏),並推出 TEMPERATURE_K (絕對溫度)。

  2. 代理商傳送要求來更新設定時:

    • 服務收到 TEMPERATURE_K 的要求 (例如 295.15 K)。

    • 服務的邏輯會將這個值轉換為攝氏 (22°C),並在內部更新及保存這兩個值,以維持舊版用戶端的一致性。

  3. 如果代理程式從舊版用戶端傳送已淘汰設定的要求:

    • 服務收到 TEMPERATURE_C 的要求 (例如 24°C)。
    • 這項服務會將此值轉換為絕對溫度 (297.15 K),並在內部更新及保存這兩個值。

這項雙重寫入策略可確保所有用戶端 (無論發布時間表為何) 都能讀取一致且準確的資料,避免因外部發布不符而導致中斷。

實作用戶端

如要實作與 User Preferences 代理互動的用戶端 (例如 HMI):

  1. 定義用戶端服務套件,與特定使用者偏好設定介面互動。在 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
    }
    
  2. 如要變更要求設定,請按照下列步驟操作:UserPreferencesManagementService

    1. 連線至「UserPreferencesManagementService」。
    2. 建立 RequestSettingsChangeRequest 的執行個體,並指定 SettingsGroupId 和所需設定。
    3. 選用。將 ChangePersistencePolicy 設為 PERSISTENT_CHANGE (預設) 或 NON_PERSISTENT_CHANGE
    4. 呼叫 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?;
    
  3. 使用 user_preferences_management_service.protochange_notifier.proto 訂閱設定變更:

    1. 在服務套件中,從 ChangeNotifier 介面實作 OnSettingsChange RPC。發生變更時,使用者偏好設定代理程式會叫用這個方法。
    2. 連線至「UserPreferencesManagementService」。
    3. 建立 SubscribeToSettingsChangeAndGetSettingsRequest,指定要監控的SettingsGroupId
    4. 如要傳回設定的狀態,然後透過 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?;
    
  4. 選用:管理使用者設定檔的用戶端 (例如專屬設定應用程式) 可以使用 user_preferences_admin_service.proto 與管理服務互動:

    1. 連線至「UserPreferencesAdminService」。
    2. 使用 CreateUserSelectUserDeleteUserFactoryResetListUsers 等 RPC 管理使用者設定檔。

    以下是建立使用者的概念性 Rust 範例:

    admin_service_client.CreateUser(&CreateUserRequest {
        user: Some(User {
            id: 1,
            flags: UserFlags::DRIVER.value(),
            ..Default::default()
        }).into(),
        ..Default::default()
    }).await?;
    
  5. 透過管理服務探索可用的設定和使用者:user_preferences_admin_service.proto

    1. 呼叫 UserPreferencesAdminService 上的 ListUsers(),取得所有已註冊使用者的清單。
    2. 呼叫 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);
    }
    

流程摘要

  1. 用戶端會啟動並連線至代理程式的管理和管理服務。
  2. 用戶端會探索並訂閱設定群組,並顯示初始狀態。
  3. 用戶端會將 RequestSettingsChange 傳送給代理程式。
  4. 服務專員會向用戶端傳送 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. 車輛發動時的事件。