توفّر خدمة الإعداد والمعايرة (ConCal) على منصة المركبات المحدّدة بالبرامج (SDV) إمكانات لإعداد خدمات المركبات المحدّدة بالبرامج وفقًا لمواصفات المركبة ولوائح البلد والميزات التي طلبها العميل. هذه الخدمة هي وحدة أساسية في منصة SDV، وتتيح للمصنّعين الأصليين للجهاز إعادة استخدام رمز الخدمة نفسه في عدة مركبات من خلال ضبطها وإتاحة إعادة الضبط من مصادر متعددة (على سبيل المثال، في المصنع أو في مركز الصيانة أو من السحابة الإلكترونية).
توفّر منصة SDV واجهات برمجة تطبيقات موجّهة للخدمات من أجل ضبط حِزم الخدمات ومعايرتها على مركبة معيّنة. باستخدام هذه الواجهة، يمكن لمصنّعي المعدات الأصلية تنفيذ إعدادات ومعايرة خاصة بهم.
تتضمّن خدمة "الإعداد والمعايرة" العمليات التالية:
الإعداد: يشمل تحديد الخصائص الأساسية وسلوك المركبة، وقد يعتمد على عوامل متعددة، مثل الموقع الجغرافي للمركبة أو الخيارات التي يطلبها المستخدم أو اللوائح التنظيمية في البلد. ويحدّد هذا المعيار طريقة تفاعل المكوّنات ويفرض إعدادات البرامج التي تؤثّر في الوظائف العامة للمركبة، مثل أنواع البرامج واتصالات الشبكة ومعلمات التشغيل الأولية.
المعايرة: وهي عملية ضبط دقيق لمعلمات النظام ضمن النطاقات المحدّدة مسبقًا. على سبيل المثال، تعمل عملية المعايرة على تعديل دقة المستشعرات والمشغّلات، وتحسين أداء المحرك للتحكّم في الانبعاثات، وتحسين استجابات أنظمة السلامة وسهولة القيادة. تحدّد عملية الضبط الإطار الأساسي لطريقة عمل المركبة، بينما تعمل عملية المعايرة على تحسين سلوكها ضمن هذا الإطار. وكلاهما ضروريان لضمان استيفاء المركبات للوائح الانبعاثات وتحقيق أفضل أداء وتعزيز السلامة، ويمكنهما تعويض التآكل بمرور الوقت.
من خلال توفير واجهة برمجة تطبيقات ConCal موحّدة على مستوى SDV، نسهّل عملية تنفيذ حِزم خدمات SDV، ما يجنّب الحاجة إلى إعادة تنفيذ إمكانات الإعداد والمعايرة لتشغيلها على مركبات مختلفة من مصنّعين مختلفين للمعدات الأصلية.
هندسة معمارية
يمكن أن تحتوي كل حِزمة خدمات على عنصر واحد أو أكثر من عناصر الضبط.
عناصر الضبط
يتألف عنصر إعداد (config) من مَعلمة إعداد واحدة أو أكثر وقيمها. الإعداد هو رسالة protobuf خاصة بالخدمة يمكن أن تتضمّن حقولها رسائل protobuf متداخلة (struct) أو خرائط أو مصفوفات أو أعدادًا صحيحة أو أعدادًا عشرية أو قيمًا منطقية أو وحدات بايت أو مَعلمات سلسلة.
// 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.
التقييدات:
- يجب أن يبدأ اسم المثيل بحرف.
- يجب أن تكون جميع الأحرف أبجدية رقمية صغيرة أو شرطة واصلة.
- يجب ألا تظهر الشرطات المتصلة في الاسم بشكل متتالٍ أكثر من مرة واحدة.
- يجب ألا ينتهي اسم الإعداد بشرطة.
- يجب ألا يزيد طول اسم الإعداد عن 48 حرفًا.
- يجب أن تكون أسماء الإعدادات فريدة على الجهاز الافتراضي نفسه لحزمة الخدمة نفسها.
قبل الإصدار 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: تسجيل الإعداد التلقائي واسترداد الإعداد المخصّص
يتيح ذلك لمصنّعي المعدات الأصلية تنفيذ حِزم الخدمات مرة واحدة وتنفيذها على عدة مركبات.
التفعيل
يمكن أن تحتفظ ConCal بنسخة واحدة أو أكثر من الخادم على منصة SDV. يجب أن تكتشف حِزم الخدمات خادم ConCal الأقرب وتستخدمه. على سبيل المثال، إذا تم نشر ConCal مرة واحدة لكل وحدة تحكّم إلكترونية (ECU)، يجب أن تتمكّن حزمة الخدمة من الوصول إلى نسخة ConCal التي تعمل على وحدة التحكّم الإلكترونية نفسها. يتيح ذلك لحزمة الخدمة استرداد الإعدادات في الوقت المناسب. إذا لم يكن لدى مثيل ConCal إعدادات مطلوبة (لأنّها تقع ضمن نطاق مسؤولية ConCal آخر)، يطلب خادم ConCal الذي تم التواصل معه هذه الإعدادات من مثيل ConCal المالك ويعيد توجيهها إلى حزمة الخدمة.
تخصيص الإعدادات
وتُعد إمكانية إعادة استخدام البرنامج نفسه مع مركبات مختلفة إحدى المزايا الرئيسية للمركبات المحدّدة بالبرامج. يتم تطوير البرنامج مرة واحدة ثم إعادة استخدامه في عدة مركبات، ويمكننا تعديل سلوك البرنامج استنادًا إلى مواصفات المركبة. هذا هو الغرض الأساسي من ConCal، وهو حساب إعدادات الخدمة استنادًا إلى خصائص المركبة بمساعدة عمليات إلغاء الإعدادات.
ConfigOverride هي رسالة protobuf تصف كيفية تعديل الإعدادات لتناسب المركبة المحدّدة. ويتألف من معرّف إلغاء، وهو معرّف فريد تحدّده الجهة التي تقدّمه، ومعرّف إعدادات، وقائمة ConfigOverrideKeyValuePair. لا يمكن توفير ConfigOverride إلا
أثناء عملية التحديث ومن خلال الخدمات المسموح بها فقط، والتي يحدّدها
مصنّع المعدات الأصلية. يتم توفير تعريفات protobuf لكلتا البنيتَين أدناه.
// 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 العمليات التالية:
تعيين قيمة جديدة: يتم إيقاف القيمة الأخيرة نهائيًا ويتم تعيين قيمة جديدة للمعلمة، مثل تعيين قيمة جديدة لحقل بسيط (عدد صحيح أو سلسلة أو عدد عشري أو قيمة منطقية أو وحدات بايت) أو إعادة كتابة الحقول المعقّدة، مثل الخرائط أو القوائم أو البُنى أو الإعداد الكامل.
إزالة قيمة أو تنظيفها: يتم توفير هذه العملية لجميع الأنواع، بما في ذلك الرسائل والحقول المتكررة والخرائط والحقول الفردية والإعداد نفسه. يمكن إجراء هذه العملية على حقل أو رسالة كاملة، ما يعني أنّه لا يمكن إزالة مفاتيح معيّنة في خريطة وعناصر فردية من حقل متكرّر.
أضِف قيمة جديدة إلى خريطة.
إعادة كتابة قيمة مفتاح حالي في خريطة (يمكن أيضًا تنفيذ هذه العملية كإضافة قيمة جديدة إلى خريطة، إذا لم يكن المفتاح متوفّرًا)
ضبط حزمة خدمات باستخدام 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.bp:rust_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
في وقت التشغيل، عليك إعداد مكوّنات البرامج الوسيطة المطلوبة لاستدعاءات ConCal RPC. في هذا المثال، نستهلّ بشكل غير متزامن عند بدء الحزمة.
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قبل المتابعة إلى منطق الحزمة الرئيسي. 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، لأنّه تم تسجيل الإعدادات من قبل، باستخدام الحزمة نفسها. تسمح عملية الإعداد لحزمة الخدمات بالتعامل بشكل غير شفاف مع كلتا الحالتين، وهما الإعدادات الأصلية والإعدادات التي تم تجاهلها، وتبقى منطق أعمال حزمة الخدمات بدون تغيير.تعرض الدالة
GetConfigRequestوحدات بايت أولية. تواصل الدالةget_configتحليلها إلى نوع الإعدادات المتوقّع.