บริการกำหนดค่าและการปรับเทียบ (ConCal) ในแพลตฟอร์มยานพาหนะที่กำหนดโดยซอฟต์แวร์ (SDV) มีความสามารถในการกำหนดค่าบริการ SDV ตามข้อกำหนดของยานพาหนะ กฎระเบียบของประเทศ และฟีเจอร์ที่ลูกค้าสั่ง บริการนี้เป็นองค์ประกอบพื้นฐานของแพลตฟอร์ม SDV ซึ่งช่วยให้ OEM สามารถนำโค้ดบริการเดียวกันไปใช้ซ้ำในรถยนต์หลายคันได้โดยการกำหนดค่า และเปิดใช้การกำหนดค่าใหม่จากหลายแหล่งที่มา (เช่น ที่โรงงาน ในศูนย์บริการ จากระบบคลาวด์)
แพลตฟอร์ม SDV มี API ที่หันหน้าเข้าหาบริการสำหรับการกำหนดค่าและการปรับเทียบ ของชุดบริการในยานพาหนะที่เฉพาะเจาะจง อินเทอร์เฟซนี้ช่วยให้ OEM สามารถ ใช้ตรรกะการกำหนดค่าและการปรับเทียบเฉพาะ OEM
บริการการกำหนดค่าและการปรับเทียบประกอบด้วยกระบวนการต่อไปนี้
การกำหนดค่า ซึ่งเกี่ยวข้องกับการกำหนดพร็อพเพอร์ตี้พื้นฐานและ ลักษณะการทำงานของยานพาหนะ และอาจขึ้นอยู่กับหลายปัจจัย เช่น ตำแหน่งของยานพาหนะ ตัวเลือกที่ผู้ใช้สั่ง หรือกฎระเบียบของประเทศ โดยจะกำหนดวิธีที่ คอมโพเนนต์โต้ตอบกันและกำหนดการตั้งค่าซอฟต์แวร์ที่มีผลต่อ ฟังก์ชันการทำงานโดยรวมของรถยนต์ เช่น ตัวแปรซอฟต์แวร์ การเชื่อมต่อ เครือข่าย และพารามิเตอร์การทำงานเริ่มต้น
การปรับเทียบ ซึ่งปรับแต่งพารามิเตอร์ของระบบภายในช่วงที่กำหนดค่าไว้ล่วงหน้า ตัวอย่างเช่น การปรับเทียบจะปรับความแม่นยำของเซ็นเซอร์และตัวกระตุ้น เพิ่มประสิทธิภาพเครื่องยนต์เพื่อควบคุมการปล่อยก๊าซ และปรับปรุงการขับขี่ และการตอบสนองของระบบความปลอดภัย การกำหนดค่าจะกำหนดกรอบการทำงานพื้นฐานของ ยานพาหนะ ส่วนการปรับเทียบจะเพิ่มประสิทธิภาพลักษณะการทำงานภายในกรอบการทำงานนั้น ทั้ง 2 อย่างนี้มีความสำคัญอย่างยิ่งในการรับรองว่ายานพาหนะเป็นไปตามกฎระเบียบด้านการปล่อยก๊าซเรือนกระจก เพิ่มประสิทธิภาพสูงสุด ปรับปรุงความปลอดภัย และชดเชยการสึกหรอเมื่อเวลาผ่านไป
การจัดหา ConCal API มาตรฐานทั่วทั้ง SDV ช่วยให้เราลดความซับซ้อนในการติดตั้งใช้งาน ชุดบริการ SDV โดยไม่จำเป็นต้องติดตั้งใช้งานความสามารถในการกำหนดค่าและการ ปรับเทียบใหม่เพื่อเรียกใช้ในยานพาหนะต่างๆ จาก OEM ที่แตกต่างกัน
สถาปัตยกรรม
แต่ละแพ็กเกจบริการจะมีอาร์ติแฟกต์การกำหนดค่าได้อย่างน้อย 1 รายการ
อาร์ติแฟกต์การกำหนดค่า
อาร์ติแฟกต์การกำหนดค่า (Config) ประกอบด้วยพารามิเตอร์การกำหนดค่าอย่างน้อย 1 รายการและค่าของพารามิเตอร์เหล่านั้น การกำหนดค่าคือข้อความ Protobuf เฉพาะบริการที่มีฟิลด์ซึ่งอาจมี ข้อความ Protobuf ที่ซ้อนกัน (struct), แมป, อาร์เรย์, int, float, bool, ไบต์ หรือพารามิเตอร์สตริง
// Example of a configuration message.
message SampleServiceBundleConfig
{
bool bool_parameter = 1;
int64 int_parameter = 2;
float float_parameter = 3;
string str_parameter = 4;
repeated string list_parameter = 5;
map<string, int32> map_parameter = 6;
SomeNestedMessage nested_parameter = 7;
SomeComplexMessage complex_parameter = 8;
some.nested.package.SomeNestedMessage nested_package = 9;
bytes bytes_parameter = 10;
}
ตัวระบุการกำหนดค่า
การกำหนดค่าแต่ละรายการจะมีตัวระบุที่ไม่ซ้ำกัน ตัวระบุนี้ประกอบด้วย
ชื่ออินสแตนซ์แบบเต็มของเจ้าของแพ็กเกจบริการและชื่อการกำหนดค่า ชื่อการกำหนดค่าต้องอ่านได้ง่าย ไม่ซ้ำกันต่อแพ็กเกจบริการ
และเป็นไปตามมาตรฐานการตั้งชื่อที่กำหนดไว้ใน
แบบแผนการตั้งชื่อแพ็กเกจบริการ
เช่น shared, private, diagnostics และ calibration
ข้อจำกัด:
- ชื่ออินสแตนซ์ต้องขึ้นต้นด้วยตัวอักษร
- อักขระทั้งหมดต้องเป็นตัวอักษรพิมพ์เล็ก ตัวเลข หรือขีดกลาง
- ขีดกลางในชื่อต้องไม่ปรากฏติดต่อกันมากกว่า 1 ครั้ง
- ชื่อการกำหนดค่าต้องไม่ลงท้ายด้วยยัติภังค์
- ชื่อการกำหนดค่าต้องมีความยาวไม่เกิน 48 อักขระ
- ชื่อการกำหนดค่าต้องไม่ซ้ำกันใน VM เดียวกันสำหรับแพ็กเกจบริการเดียวกัน
ก่อน 26Q2 รหัสการกำหนดค่าจะกำหนดไว้ดังนี้
// Unique identifier for the config.
message ConfigId {
// The FQIN of the service bundle that owns the configuration.
com.sdv.google.sd_common.ServiceFqin service_fqin = 1;
// The name of the config.
string config_name = 2;
}
เจ้าของการกำหนดค่าในเวลาบูตจะทราบเฉพาะสคีมาของการกำหนดค่าและ ค่าเริ่มต้นของการกำหนดค่า หากต้องการปรับแต่งลักษณะการทำงานของแพ็กเกจบริการให้เข้ากับรถยนต์ปัจจุบัน แพ็กเกจบริการที่เป็นเจ้าของจะต้องลงทะเบียนการกำหนดค่าเริ่มต้น พร้อมกับสคีมา
รูปที่ 1 การลงทะเบียนการกำหนดค่าเริ่มต้นและการดึงข้อมูลการกำหนดค่าที่กำหนดเอง
ซึ่งช่วยให้ OEM สามารถใช้ชุดบริการเพียงครั้งเดียวและดำเนินการในยานพาหนะหลายคันได้
การทำให้ใช้งานได้
ConCal สามารถดูแลอินสแตนซ์เซิร์ฟเวอร์อย่างน้อย 1 รายการในแพลตฟอร์ม SDV แพ็กเกจบริการ ควรค้นหาและใช้เซิร์ฟเวอร์ ConCal ที่ใกล้ที่สุด ตัวอย่างเช่น หากมีการติดตั้งใช้งาน ConCal หนึ่งรายการต่อ ECU หนึ่งตัว ชุดบริการควรมีสิทธิ์เข้าถึงอินสแตนซ์ ConCal ที่ทำงานใน ECU เดียวกัน ซึ่งจะช่วยให้ชุดบริการดึงข้อมูล การกำหนดค่าได้ทันท่วงที หากอินสแตนซ์ ConCal ไม่มี การกำหนดค่าที่ขอ (เนื่องจากอยู่ในขอบเขตความรับผิดชอบของ ConCal อื่น) เซิร์ฟเวอร์ ConCal ที่ได้รับการติดต่อจะขอการกำหนดค่าจากอินสแตนซ์ ConCal ที่เป็นเจ้าของ และเปลี่ยนเส้นทางไปยังแพ็กเกจบริการ
การปรับแต่งการกำหนดค่า
ความสามารถในการนำซอฟต์แวร์เดียวกันมาใช้กับยานพาหนะที่แตกต่างกันเป็นหนึ่งใน ข้อได้เปรียบหลักของ SDV เราพัฒนาซอฟต์แวร์เพียงครั้งเดียวแล้วนำไปใช้ซ้ำในรถยนต์หลายคันได้ และปรับลักษณะการทำงานของซอฟต์แวร์ตามรายละเอียดของรถยนต์ แต่ละคันได้ นี่คือวัตถุประสงค์หลักของ ConCal ซึ่งจะคำนวณการกำหนดค่าบริการตามพร็อพเพอร์ตี้ของยานพาหนะโดยใช้การลบล้างการกำหนดค่า
ConfigOverride คือข้อความ protobuf ที่อธิบายวิธีกำหนดค่า
ให้เหมาะกับยานพาหนะที่เฉพาะเจาะจง ประกอบด้วยรหัสการลบล้าง ซึ่งกำหนดโดยเอนทิตีที่ระบุอย่างไม่ซ้ำกัน ตัวระบุการกำหนดค่า และรายการ ConfigOverrideKeyValuePair ConfigOverride จะใช้ได้เฉพาะ
ในระหว่างกระบวนการอัปเดตและเฉพาะบริการที่ได้รับอนุญาต ซึ่ง OEM เป็นผู้สร้างขึ้น คำจำกัดความของ Protobuf สำหรับทั้ง 2 โครงสร้างมีดังนี้
// Key-value pair to update configuration.
message ConfigOverrideKeyValue {
string key = 1;
oneof value {
string value_txtproto = 2;
.google.protobuf.Any value_any = 3;
}
}
// A collection of changes for a specific configuration which should be atomically applied.
message ConfigOverride {
string override_id = 1;
ConfigId config_id = 2;
repeated ConfigOverrideKeyValue pairs = 3;
}
ConfigOverride รองรับการดำเนินการต่อไปนี้
กำหนดค่าใหม่: ค่าสุดท้ายจะเลิกใช้งานและพารามิเตอร์จะได้รับ การกำหนดค่าใหม่ เช่น การกำหนดค่าใหม่ให้กับฟิลด์อย่างง่าย (int, string, float, bool, bytes) หรือการเขียนฟิลด์ที่ซับซ้อนใหม่ เช่น แมป รายการ โครงสร้าง หรือการกำหนดค่าที่สมบูรณ์
นำค่าออกหรือล้างค่า: การดำเนินการนี้มีให้สำหรับทุกประเภท รวมถึงข้อความ ฟิลด์ที่ซ้ำกัน แมป ฟิลด์เดี่ยว และ การกำหนดค่าเอง การดำเนินการนี้สามารถทำได้ในฟิลด์หรือ ข้อความที่สมบูรณ์ ซึ่งหมายความว่าระบบไม่รองรับการนำคีย์ที่เฉพาะเจาะจงในแมปและ องค์ประกอบแต่ละรายการออกจากฟิลด์ที่มีซ้ำ
เพิ่มค่าใหม่ลงในแผนที่
เขียนค่าของคีย์ที่มีอยู่ในแมปใหม่ (การดำเนินการนี้ยังสามารถ ดำเนินการเป็นการเพิ่มค่าใหม่ลงในแมปได้ หากไม่มีคีย์อยู่)
กำหนดค่าแพ็กเกจบริการโดยใช้ ConCal
บทนี้อธิบายวิธีพัฒนาแพ็กเกจบริการที่เรียกการกำหนดค่าในรันไทม์ จากมุมมองของแพ็กเกจที่กำหนดค่าได้ จะไม่สามารถระบุได้ว่าการกำหนดค่าที่ดึงมาเป็นการตั้งค่าเริ่มต้นจากโรงงาน หรือเป็นการแก้ไขในภายหลังโดยใช้กระบวนการลบล้างและการปรับเทียบ ConCal
คุณดูตัวอย่างที่ใช้เป็นพื้นฐานของเอกสารประกอบได้ที่
system/software_defined_vehicle/samples/concal/src/concal_client
ดูรายละเอียดเพิ่มเติมได้ที่การพัฒนาแพ็กเกจบริการ
ประกาศประเภทการกำหนดค่าที่เป็นของแพ็กเกจบริการ
ชุดบริการเป็นเจ้าของประเภทของการกำหนดค่าที่ดึงมา ซึ่งจะช่วยให้สามารถอัปเดตแพ็กเกจที่กำหนดค่าได้โดยไม่ขึ้นกับข้อมูลการกำหนดค่าที่บันทึกไว้
เขียนไฟล์ Protobuf (มีนามสกุล
.proto) ที่ประกาศประเภทการกำหนดค่าsyntax = "proto3"; package android.sdv.demo.config; message RearViewCamera { string model = 1; uint64 horizontal_resolution = 2; uint64 vertical_resolution = 3; float x_axis_field_of_view = 4; float y_axis_field_of_view = 5; bool is_rgb = 6; }สร้างเป้าหมายการสร้างที่สร้างไลบรารีรันไทม์ที่อนุญาตให้ดึงข้อมูลประเภทการกำหนดค่า ในไฟล์
Android.bprust_protobuf { name: "libsdvtestconcal_proto_rust", crate_name: "sdvtestconcal_proto_rust", protos: [ "rear_view_camera.proto", ], proto_flags: [ "-I external/protobuf/src", "-I .", ], source_stem: "sdvtestconcal_proto_rust", vendor_available: true, product_available: true, min_sdk_version: "35", }
สร้างโค้ดมิดเดิลแวร์ ConCal RPC สำหรับแพ็กเกจ
เพิ่มประกาศ VSIDL ลงในแพ็กเกจบริการ
package: "com.sdv.oem.sample.concal"
service_bundle {
name: "SampleOemConCalClientServiceBundle"
client {
service: "com.sdv.google.concal.ConCalRegistrationService"
}
}
ประกาศว่าแพ็กเกจเป็นไคลเอ็นต์ของบริการลงทะเบียนและเรียกข้อมูลการกำหนดค่า ConCal ดูภาพรวมของ VSIDL และมิดเดิลแวร์เพื่อดูภาพรวมของการใช้ VSIDLC เพื่อสร้างการเชื่อมโยงไคลเอ็นต์ RPC สำหรับแพ็กเกจ
เริ่มต้นมิดเดิลแวร์สำหรับการดึงข้อมูลการกำหนดค่า RPC
ในเวลาที่รัน ให้เริ่มต้นคอมโพเนนต์มิดเดิลแวร์ที่จำเป็นสำหรับการเรียก RPC ของ ConCal ในตัวอย่างนี้ เราจะเริ่มต้นแบบไม่พร้อมกันเมื่อเริ่ม Bundle
pub struct ExampleConcalBundle {
context: ContextRef,
runtime: Option<Runtime>,
}
sdv::lifecycle::register_service_bundle!(ExampleConcalBundle);
impl ServiceBundle for ExampleConcalBundle {
fn new(context: ContextRef) -> ExampleConcalBundle {
info!("Creating {}.", context.get_self_fqin());
ExampleConcalBundle { context, runtime: None }
}
fn on_start(&mut self) {
let fqin = self.context.get_self_fqin();
info!("Starting {}.", fqin);
let runtime = Builder::new_multi_thread()
.worker_threads(4)
.thread_name("tokio-pool")
.enable_all()
.build()
.unwrap();
let context = self.context;
runtime.spawn(async move {
let registration_client = setup_register_config_rpc(context).await;
/* main SB logic here */
});
self.runtime = Some(runtime);
}
async fn setup_register_config_rpc(context: ContextRef) -> RegistrationClient {
let sdv_comms = SdvComms { context };
let sd = ServiceDiscoveryManager::new(context);
let unit_name_args = UnitNameDiscoveryArgs::new_builder()
.set_sdv_package_name("com.sdv.oem.sample.concal")
.set_service_bundle_name("SampleOemConCalServiceBundle")
.set_service_unit_name(RegistrationClient::DEFAULT_UNIT_NAME)
.build()
.unwrap();
let mut unit_name_stream =
sd.subscribe_service_unit_change_by_name(&unit_name_args).await.unwrap();
// wait until RPC servers are registered, if server is not a custom agent
while let Some(event) = unit_name_stream.next().await {
if let ServiceUnitChangeEvent::Registered(sud) = event {
let service_identity = sud.get_service_bundle_identity();
let fqin = service_identity.get_fqin();
if fqin.get_sdv_package_name() == "com.sdv.oem.sample.concal"
&& fqin.get_service_bundle_name() == "SampleOemConCalServiceBundle"
&& fqin.get_service_instance_name() == "default"
{
break;
}
}
}
let service_bundle =
SampleOemConCalClientServiceBundle::new(Arc::new(sdv_comms)).await.unwrap();
service_bundle
.create_rpc_client::<RegistrationClient>(
UnitName::builder()
.package_name("com.sdv.oem.sample.concal")
.bundle_name("SampleOemConCalServiceBundle")
.service_unit_name(RegistrationClient::DEFAULT_UNIT_NAME)
.build()
.unwrap(),
ClientOptions::default(),
)
.await
.expect("Failed to create an RPC client")
}
สถานที่:
- ใน
on_startเราจะสร้างรันไทม์ของ Tokio และเรียกใช้ฟังก์ชัน Tokio งาน เรียกsetup_register_rpcก่อนดำเนินการตามตรรกะหลักของ Bundle setup_register_rpcตั้งค่าการเชื่อมโยง RPC ของมิดเดิลแวร์ โปรดทราบว่า ตัวอย่างไม่ได้ถือว่าเอเจนต์เป็นผู้ใช้ฟังก์ชัน ConCal เซิร์ฟเวอร์อาจพร้อมใช้งานหลังจากที่เริ่มใช้แพ็กเกจแล้วเท่านั้น ดังนั้น โค้ดตัวอย่างจึงรอให้ลงทะเบียนเซิร์ฟเวอร์ RPC โดยใช้ Service Discovery API
เริ่มต้นการกำหนดค่า
ลงทะเบียนอาร์ติแฟกต์การกำหนดค่าโดยระบุรหัส สคีมาการกำหนดค่า และค่าการกำหนดค่าเริ่มต้นจากโรงงานให้กับเซิร์ฟเวอร์ ConCal
หากมีการเรียกใช้การลงทะเบียนเป็นครั้งแรก ระบบจะบันทึกค่าเริ่มต้น หากมีการเรียกการลงทะเบียนเมื่อเริ่มใช้ชุดในภายหลัง ระบบจะเรียกค่าเริ่มต้นที่คงอยู่พร้อมการลบล้าง ConCal ที่ใช้ (หากมี)
การเรียกใช้การกำหนดค่ารีจิสทรี ConCal จะไม่ล้มเหลว ไม่ว่าอาร์ติแฟกต์จะลงทะเบียนแล้วหรือไม่ก็ตาม
impl ServiceBundle for ExampleConcalBundle{
/* ... */
fn on_start(&mut self) {
/* ... */
runtime.spawn(async move {
let registration_client = setup_register_config_rpc(context).await;
sample_concal_main(fqin, registration_client).await
});
self.runtime = Some(runtime);
}
}
async fn sample_concal_main(
fqin: ServiceFqin,
registration_client: RegistrationClient,
) -> sdv::status::SdvResult<()> {
let config = get_rear_view_camera_factory_config();
let config_id = get_config_id(&fqin);
register_config(®istration_client, &config_id, &config).await;
/* ... */
}
fn get_rear_view_camera_factory_config() -> RearViewCamera {
RearViewCamera {
model: String::from("model 1"),
horizontal_resolution: 720,
vertical_resolution: 720,
x_axis_field_of_view: 70.0,
y_axis_field_of_view: 70.0,
is_rgb: false,
..Default::default()
}
}
fn get_config_id(fqin: &ServiceFqin) -> ConfigId {
ConfigId {
config_name: "config".to_string(),
service_fqin: MessageField::some(ProtoFqin {
vm_name: fqin.get_sdv_vm_name().to_string(),
package_name: fqin.get_sdv_package_name().to_string(),
service_name: fqin.get_service_bundle_name().to_string(),
instance_name: fqin.get_service_instance_name().to_string(),
..Default::default()
}),
..Default::default()
}
}
async fn register_config(
client: &RegistrationClient,
config_id: &ConfigId,
config: &RearViewCamera,
) {
let config_fd = FileDescriptorSet {
file: vec![RearViewCamera::descriptor().file_descriptor_proto().clone()],
..Default::default()
};
let config = Any::pack(config).expect("Failed to pack config");
let config_metadata = ConfigMetadata {
descriptor_set: MessageField::some(config_fd),
default_config: MessageField::some(config.clone()),
..Default::default()
};
client
.RegisterConfigMetadata(&RegisterConfigMetadataRequest {
config_id: MessageField::some(config_id.clone()),
metadata: MessageField::some(config_metadata),
config_version: String::from("1.0"),
..Default::default()
})
.await
.expect(
"RegisterConfigMetadata should not fail, even if configuration was registered before",
);
}
สถานที่:
- งานที่สร้างขึ้นใน
on_startหลังจากดึงข้อมูลการเชื่อมโยง RPC แล้ว จะดำเนินการตามตรรกะทางธุรกิจโดยเรียกใช้sample_concal_main sample_concal_mainเริ่มต้นด้วยการลงทะเบียนอาร์ติแฟกต์การกำหนดค่า ตรรกะ อยู่ในregister_config- หากต้องการลงทะเบียนการกำหนดค่า บันเดิลต้องระบุประเภท Protobuf, รหัส และค่าเริ่มต้น
config_fdคือประเภทการกำหนดค่า การเป็นเจ้าของแพ็กเกจที่มีการกำหนดค่า ประเภทช่วยให้มั่นใจได้ว่าจะมีการเรียกข้อมูลสคีมาที่แพ็กเกจคาดหวังเสมอ รวมถึง การอัปเดต APEX- ระบบจะใช้รหัสเป็นตัวระบุในตรรกะการคงอยู่ภายในของเซิร์ฟเวอร์ ConCal
- ค่าเริ่มต้นจะสร้างขึ้นใน
get_rear_view_camera_factory_configค่าเริ่มต้นคือค่าที่เซิร์ฟเวอร์ ConCal จัดเก็บไว้ หากก่อนหน้านี้ไม่มีการจัดเก็บค่าใดไว้ ซึ่งเป็นวิธีหนึ่งที่ระบบสามารถ ระบุการกำหนดค่าจากโรงงาน คุณยังตั้งค่าแบบอื่นๆ ได้ด้วย
เรียกข้อมูลการกำหนดค่า
หลังจากลงทะเบียนแล้ว ให้ดึงข้อมูลการกำหนดค่า เนื่องจากมีการลงทะเบียนการกำหนดค่าไว้ก่อนหน้านี้แล้ว การเรียกนี้จึงรับประกันว่าจะสำเร็จ
async fn sample_concal_main(
fqin: ServiceFqin,
registration_client: RegistrationClient,
update_client: UpdateClient,
) -> sdv::status::SdvResult<()> {
let config = get_rear_view_camera_factory_config();
let config_id = get_config_id(&fqin);
register_config(®istration_client, &config_id, &config).await;
let config = get_config(®istration_client, &config_id).await;
info!("Retrieved configuration:\n{config:#?}");
// Onwards, use configuration in bundle's main business logic
/* ... */
}
async fn get_config(client: &RegistrationClient, config_id: &ConfigId) -> RearViewCamera {
let bytes = client
.GetConfig(&GetConfigRequest {
config_id: MessageField::some(config_id.clone()),
..Default::default()
})
.await
.expect("Get config does not fail, as config was registered before")
.config;
RearViewCamera::parse_from_bytes(&bytes).expect("parse_from_bytes failed")
}
สถานที่:
การเรียกใช้
GetConfigRequestจะสำเร็จอย่างแน่นอน เนื่องจากมีการลงทะเบียนการกำหนดค่า ไว้ก่อนหน้านี้แล้วโดยใช้ Bundle เดียวกัน การตั้งค่าช่วยให้ชุดบริการ จัดการทั้งกรณีการกำหนดค่าจากโรงงานและการกำหนดค่าที่ลบล้าง ได้อย่างไม่โปร่งใส โดยตรรกะทางธุรกิจของชุดบริการจะยังคงไม่เปลี่ยนแปลงการเรียก
GetConfigRequestจะแสดงผลไบต์ดิบ ฟังก์ชันget_configจะดำเนินการแยกวิเคราะห์ไปยังประเภทการกำหนดค่าที่คาดไว้