Шлюз SDV (Software Defined Vehicle) в информационно-развлекательной системе автомобиля обеспечивает связь между этой системой и удаленными сервисами SDV. Шлюз позволяет приложениям Java от производителя оригинального оборудования (OEM) и встроенным сервисам, таким как уровень абстрагирования оборудования автомобиля (VHAL), взаимодействовать с сервисами SDV. Шлюз использует установленные методы связи, в том числе регистрацию сервисов, обнаружение и удаленные вызовы процедур (RPC).
Этот шлюз соответствует определенным требованиям проекта AAOS SDV, например позволяет реализовать VHAL с помощью SDV Data Tunnel для получения информации о свойствах. Кроме того, она позволяет приложениям Android на Java и Kotlin, установленным на IVI, использовать стек SDV Comms, регистрироваться как сервисы, находить другие сервисы SDV и взаимодействовать с ними.
Участки кода SDV Gateway и примеры клиентов SDV Gateway можно найти в разделе Участки кода.
Модели интеграции SDV Gateway
Используйте SDV-IVI Comms через шлюз SDV в архитектуре IVI
SDV Gateway взаимодействует с приложениями Java, встроенными сервисами, стеком SDV Comms и сетью автомобиля. На рисунке 1 показаны следующие взаимодействия:
Рисунок 1. Взаимодействия с шлюзом SDV.
На этой схеме:
- Приложения взаимодействуют с SDV Gateway через клиентские сервисы.
- AAOS SDV SDK предоставляет следующие возможности:
- ISdvGateway AIDL API для межпроцессного взаимодействия.
- Библиотеки связи для взаимодействия с сетью.
- Удобный C API для интеграции с собственными сервисами.
- API ISdvGateway AIDL реализован сервисом и подсистемой SDV Gateway.
- Сервис SDV Gateway управляет:
- Двустороннее обнаружение сервисов.
- Обмен данными с удаленными сервисами SDV.
- Основная бизнес-логика.
- Подсистема SDV Gateway подключается к сети автомобиля.
- Встроенные сервисы, в том числе реализация VHAL, могут использовать API ISdvGateway AIDL напрямую или через C API SDK.
- Прокси-сервер VHAL служит эталонной реализацией VHAL, включающей интеграцию сопоставления VSIDL.
Модель интеграции для шлюза SDV в собственном сервисе IVI
Модель интеграции показана на рисунке 2.
Рисунок 2. Модель интеграции SDV Gateway.
Как использовать SDV Gateway в оригинальном сервисе IVI
На рисунке 3 показано, как использовать шлюз SDV в IVI:
Рисунок 3. SDV Gateway на IVI.
Условия тестирования
Запустите пул потоков Binder:
Клиентской библиотеке SDV Gateway требуется запущенный пул потоков Binder для получения асинхронных обратных вызовов от сервисов Binder.
API, необходимый для создания клиента SDV Gateway, не работает, если не запущен пул потоков Binder.
Как добавить клиентскую библиотеку шлюза
Клиентская библиотека Native Gateway предоставляет API C. Добавьте экземпляр libsdvgatewayclient в качестве зависимости, чтобы использовать C API:
cc_binary {
name: "your_binary_name",
srcs: ["main.cpp"],
shared_libs: [
"libsdvgatewayclient",
],
}
Как загрузить клиент Gateway
#include "libsdvgatewayclient.h"
Как создать экземпляр нативного клиента
ASDVGateway_Client* client;
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_new(
&client, /*outStatus*/nullptr);
После создания клиента:
Содержит статус для всех последующих взаимодействий с сервисом Gateway.
Используется как контекст для всех взаимодействий с другими сервисами, поддерживающими SDV, и передается в качестве первого параметра функциям C API.
Коды статусов и сообщения об ошибках
Большинство функций C API имеют следующее определение:
ASDVGateway_StatusCode_t ApiFunctionName(..., ASDVGateway_Status_t* outStatus);
Чтобы оценить успешность операции, проверьте возвращенный код статуса, который имеет тип ASDVGateway_StatusCode_t. Вы также можете передать указатель на структуру, в которой функция может заполнить код статуса и сообщение об ошибке. Указатель передается в качестве последнего параметра с названием outStatus. Нулевое значение означает, что выходная структура не используется.
Вызывающая программа должна выделить структуру статуса памяти для сообщения об ошибке. Структура статуса может содержать как код статуса, так и сообщение об ошибке. Пример получения сообщения об ошибке при создании нового клиента доступен.
Чтобы оценить эффективность:
Проверьте возвращенный код статуса, который относится к типу
ASDVGateway_StatusCode_t.Передайте указатель на структуру, в которой функция может заполнить код статуса и сообщение об ошибке.
- Указатель передается в качестве последнего параметра с именем
outStatus. - Нулевое значение означает, что структура выходных данных не используется.
- Вызывающая программа должна выделить структуру статуса памяти для сообщения об ошибке.
- Структура статуса может содержать код статуса и сообщение об ошибке.
- Указатель передается в качестве последнего параметра с именем
Ниже приведен пример того, как получить сообщение об ошибке для нового клиента:
#include <iostream>
#include <array>
struct StatusWithErrorMsg : ASDVGateway_Status_t {
StatusWithErrorMsg() {
// Ensure the base struct pointers point to our internal buffer
errorMessage = errorMessageBuffer.data();
maxErrorMessageSize = errorMessageBuffer.size();
// Good practice: Zero-initialize the buffer
errorMessageBuffer.fill(0);
}
std::array<char, 256> errorMessageBuffer;
};
// --- Execution ---
ASDVGateway_Client* client = nullptr;
StatusWithErrorMsg status;
// Initialize the client
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_new(&client, &status);
// Log Results
std::cout << "Returned statusCode: " << statusCode << std::endl;
std::cout << "Status Struct Code: " << status.statusCode << std::endl;
std::cout << "Status Error Message: " << status.errorMessage << std::endl;
В этом примере:
Код статуса, содержащийся в структуре статуса, имеет то же значение, что и возвращенный код статуса.
Сообщение об ошибке заполняется только до символа
maxErrorMessageSize(включая нулевой завершающий символ\0). Если ошибки не происходит (код статуса –OK), сообщение об ошибке представляет собой пустую строку.
Сообщения Init
Функция Init comms инициализирует связь между вызывающим приложением и другими приложениями, используя стек связи SDV и шлюз SDV. Функцию Init comms можно вызывать в следующих контекстах:
После создания клиента.
До любых взаимодействий с туннелем данных, RPC или обнаружением сервисов.
Рассмотрим пример.
ASDVGateway_InitCommsParams_t params{
.packageName = "android.sdv.samples.gateway.client",
.serviceBundleName = "NativeTestApp",
.serviceInstanceName = "default",
};
// Initialize communications and capture the status code
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_initComms(client, ¶ms, &status);
// Recommended check
if (statusCode != ASDVGateway_StatusCode_OK) {
std::cerr << "Failed to init comms: " << status.errorMessage << std::endl;
}
Обнаружение сервисов
Вы можете получать уведомления о регистрации или отмене регистрации сервисных единиц определенного типа или с определенным названием. Функция обратного вызова получает уведомление в потоке, принадлежащем клиенту шлюза. Этот обратный вызов изначально активируется со всеми зарегистрированными единицами сервиса сразу после вызова C API. После первоначального триггера уведомления продолжают поступать с обновлениями, пока слушатель не будет явно отменен.
class NativeSdvGatewayTestApp {
public:
static void ServiceUnitChangeListenerCallback(
const ASDVGateway_ServiceUnitChangeEventType eventType,
const ASDVGateway_ServiceUnitDefinition* serviceUnitDefinition,
void* userData
) {
auto* testApp = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);
if (testApp) {
// Logic to react when services are registered or unregistered
// e.g., testApp->handleServiceChange(eventType, serviceUnitDefinition);
}
}
};
// --- Listener Registration ---
// Ensure thisApp remains in scope as long as the listener is active
NativeSdvGatewayTestApp* thisApp = get_current_app_context();
ASDVGateway_UnitType_t unitType{
.sdvPackageName = "android.sdv.samples.sdv_gateway",
.serviceBundleName = "DtPublisher",
.unitTypeName = "TirePressure",
};
// Register the listener
ASDVGateway_Client_registerListenerForServiceUnitChangeByType(
client,
&unitType,
NativeSdvGatewayTestApp::ServiceUnitChangeListenerCallback,
static_cast<void*>(thisApp), // userData
&listenerHandle,
&status
);
// --- Cleanup ---
// Unregistering stops all notification to the callback function
ASDVGateway_Client_unregisterListenerForServiceUnitChangeByType(
client,
listenerHandle,
&status
);
Чтобы получить зарегистрированные сервисные единицы без дополнительных уведомлений, используйте API ASDVGateway_Client_fetchServiceUnitsByType и ASDVGateway_Client_fetchServiceUnitsByName.
class NativeSdvGatewayTestApp {
public:
/**
* Callback triggered for each service unit found that matches the requested type.
*/
static void FetchServiceUnitsCallback(
const ASDVGateway_ServiceUnitDefinition* serviceUnitDefinition,
void* userData
) {
auto* app = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);
if (serviceUnitDefinition) {
// The service unit is registered with service discovery.
// Example: processServiceUnit(serviceUnitDefinition);
}
}
};
// --- Execution ---
ASDVGateway_UnitType_t unitType{
.sdvPackageName = "android.sdv.samples.sdv_gateway",
.serviceBundleName = "DtPublisher",
.unitTypeName = "TirePressure",
};
// Context used to differentiate between various fetchServiceUnits calls
void* userData = static_cast<void*>(thisApp);
ASDVGateway_Client_fetchServiceUnitsByType(
client,
&unitType,
NativeSdvGatewayTestApp::FetchServiceUnitsCallback,
userData,
&status
);
Обратный вызов запускается синхронно в потоке вызывающего абонента во время вызова API ASDVGateway_Client_fetchServiceUnitsByType. Используйте ASDVGateway_Client_fetchServiceUnitsByName, чтобы получить зарегистрированные сервисные единицы по названию, а не по типу единицы:
ASDVGateway_StatusCode_t ASDVGateway_Client_fetchServiceUnitsByName(
// [in] Opaque pointer to a client object.
const ASDVGateway_Client* client,
// [in] Pointer to a structure containing the package name,
// service bundle name, and service unit name.
const ASDVGateway_UnitNameDiscoveryArgs_t* unitName,
// [in] Callback function to be called for each service unit
// definition registered. Called synchronously in the
// caller's thread before the fetch completes.
ASDVGateway_FetchServiceUnitsCallback callback,
// [in] Optional value passed back as a parameter of the callback.
void* userData,
// [out] Optional pointer to status structure for result
// codes and error messages.
ASDVGateway_Status_t* outStatus
);
Процесс RPC
Клиентская библиотека шлюза обрабатывает настройки протокола TLS для обмена данными с другими приложениями, поддерживающими SDV. В частности, он получает настройки TLS, необходимые для связи. Вот как определяется использование TLS:
TLS используется, когда для режима загрузки SDV задано значение
LOCKED.Небезопасная связь используется, когда режим загрузки SDV –
UNLOCKED. При использовании TLS клиентская библиотека Gateway создает пару ключей, которая известна только процессу приложения. Клиент получает учетные данные RPC, чтобы создать сервер RPC или канал клиента RPC. Для RPC используется отдельная виртуальная локальная сеть SDV-RPC. Клиентская библиотека Gateway вызывает android_setprocnetwork, чтобы переключить сеть процесса по умолчанию на VLAN SDV-RPC во время или после вызоваASDVGateway_Client_initComms.
Доступность RPC
Клиент, настроенный на запуск при ранней загрузке, может запуститься до того, как станет доступна VLAN SDV-RPC. Прежде чем пытаться создать сокеты RPC-сервера или RPC-клиента, убедитесь, что RPC доступен:
// Check if the RPC (Remote Procedure Call) service is available
bool isRpcAvailable = ASDVGateway_Client_isRpcAvailable(client);
if (isRpcAvailable) {
// Proceed with RPC calls
} else {
// Handle the case where RPC is not yet ready or available
}
или
// Check if the process is correctly bound to the SDV RPC Network Interface/VLAN
bool isRpcNetworkBound = ASDVGateway_Client_isProcessBoundToSdvRpcNetworkInterface(client);
if (!isRpcNetworkBound) {
// Usually implies the process isn't running on the correct network interface
// or the VLAN configuration is missing.
std::cerr << "Warning: Process is not bound to the SDV RPC VLAN." << std::endl;
}
Настройте прослушиватель клиентских событий с помощью метода ASDVGateway_Client_setClientNotificationCallback, чтобы получать уведомления об изменении статуса доступности RPC.
ASDVGateway_Client_isProcessBoundToSdvRpcNetworkInterface предпочтительнее, если приложение переключает сеть процесса, поскольку оно проверяет и доступность RPC, и привязку процесса к VLAN SDV-RPC.
Как изменить сеть по умолчанию для процесса
Возможно, вам потребуется переключиться с VLAN SDV-RPC в качестве сети процесса по умолчанию
для открытия сокетов для подключений, предназначенных для интернета. Вызовите ASDVGateway_Client_unbindProcessFromSdvRpcNetworkInterface и ASDVGateway_Client_bindProcessToSdvRpcNetworkInterface, чтобы отменить привязку процесса к VLAN SDV-RPC и снова привязать его. Оба вызова работают как глобальные переключатели, изменяя сетевой интерфейс, к которому привязаны сокеты, для всех потоков процесса.
// 1. Unbind: Sockets return to the "default" network interface (e.g., wlan0, eth0)
ASDVGateway_StatusCode_t unbindStatus =
ASDVGateway_Client_unbindProcessFromSdvRpcNetworkInterface(client, &status);
// 2. Bind: Sockets are now bound to the dedicated SDV-RPC VLAN
ASDVGateway_StatusCode_t bindStatus =
ASDVGateway_Client_bindProcessToSdvRpcNetworkInterface(client, &status);
// Validation
if (bindStatus == ASDVGateway_StatusCode_OK) {
// Sockets are successfully bound to the SDV-RPC VLAN
}
Уведомления RPC
Настройте прослушиватель клиента, чтобы получать уведомления об изменениях доступности RPC и обновлениях корневых сертификатов, которые должен принять сервер RPC:
class NativeSdvGatewayTestApp {
public:
static void ClientNotificationCallback(
ASDVGateway_ClientNotificationType_t notificationType,
void* userData
) {
auto* testApp = reinterpret_cast<NativeSdvGatewayTestApp*>(userData);
if (!testApp) return;
switch (notificationType) {
case ASDVGateway_ClientNotificationType_RootCertsChanged:
std::cout << "onClientNotification: Root Certs Changed" << std::endl;
// Handle certificate rotation logic here
break;
case ASDVGateway_ClientNotificationType_RpcAvailabilityChanged:
std::cout << "onClientNotification: RPC Availability Changed" << std::endl;
// Handle reconnection or UI updates here
break;
default:
std::cout << "onClientNotification: Received Unknown Notification ("
<< notificationType << ")" << std::endl;
break;
}
}
};
// --- Registration ---
NativeSdvGatewayTestApp* thisApp = get_current_app_context();
// Register the notification callback to monitor system-level changes
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_setClientNotificationCallback(
client,
NativeSdvGatewayTestApp::ClientNotificationCallback,
static_cast<void*>(thisApp), // userData
&status
);
Поток сервера RPC
Для создания сервера RPC необходимо использовать учетные данные:
ASDVGateway_RpcCredentials_t* rpcCredentials = nullptr;
// Retrieve the credentials from the client
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_rpcCredentials(client, &rpcCredentials, &status);
// Validation: Differentiating between an error and a deliberate "insecure mode"
if (status.statusCode != ASDVGateway_StatusCode_OK) {
std::cerr << "Error retrieving credentials: " << status.errorMessage << std::endl;
return;
}
// Logic for choosing Security Credentials
if (rpcCredentials == nullptr) {
// If the call succeeded but credentials are null, the system expects insecure communication
std::cout << "Configuring for Insecure Mode" << std::endl;
} else {
std::cout << "Configuring for Secure Mode:" << std::endl;
std::cout << " Private Key: " << (rpcCredentials->privateKeyPem ? "Present" : "Missing") << std::endl;
std::cout << " RootCerts: " << rpcCredentials->rootCertsPem << std::endl;
std::cout << " CertChain: " << rpcCredentials->certChainPem << std::endl;
std::cout << " SAN: " << rpcCredentials->subjectAlternativeName << std::endl;
}
// --- Cleanup ---
// Release the memory once the RPC server/client is initialized
if (rpcCredentials != nullptr) {
ASDVGateway_Client_deleteRpcCredentials(client, rpcCredentials);
}
После того как вы создали RPC-сервер и узнали его порт прослушивания, зарегистрируйте RPC-сервер, чтобы он мог обнаруживать другие приложения:
ASDVGateway_RegisterRpcServerParams_t params{
.serviceUnitName = "android-sdv-samples-sunroof-sunroof",
.unitType = ASDVGateway_UnitType_t{
.sdvPackageName = "android.sdv.samples.sunroof",
.serviceBundleName = "SunroofServer",
.unitTypeName = "Sunroof",
},
.listeningPort = listeningPort,
.serverUnitMetadata = ASDVGateway_ServerUnitMetadata_t{
.version = 1,
},
};
// Register the RPC server with the SDV Gateway
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_registerRpcServer(
client,
¶ms,
&status
);
// Basic error handling
if (statusCode != ASDVGateway_StatusCode_OK) {
std::cerr << "Failed to register RPC server: " << status.errorMessage << std::endl;
}
Когда система обновляет корневые сертификаты во время выполнения (например, при изменении состояния виртуальной машины), RPC-серверы должны обновить список принятых сертификатов:
Настройте прослушиватель клиентских событий с помощью метода
ASDVGateway_Client_setClientNotificationCallback, чтобы получать уведомления об обновлении корневых сертификатов.Позвоните по номеру
ASDVGateway_Client_rpcCredentials, чтобы получить обновленные корневые сертификаты.
Путь клиента RPC
Вот как работает клиент RPC:
ASDVGateway_FindRpcServerByNameParams_t params{
.packageName = "android.sdv.samples.cluster",
.serviceBundleName = "ClusterServer",
.serviceUnitName = "android-sdv-samples-cluster-cluster",
};
ASDVGateway_SocketAddress_t socketAddress;
ASDVGateway_RpcCredentials_t* rpcCredentials = nullptr;
// Perform the lookup to find the server's location and security requirements
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_findRpcServerByName(
mClient,
¶ms,
&socketAddress,
&rpcCredentials,
&status
);
// Mandatory check: Distinguish between system errors and intentional "Insecure Mode"
if (status.statusCode != ASDVGateway_StatusCode_OK) {
std::cerr << "Failed to find RPC server: " << status.errorMessage << std::endl;
return;
}
// Logic for establishing the RPC channel
if (rpcCredentials == nullptr) {
std::cout << "Connecting via Insecure Mode to "
<< socketAddress.address << ":" << socketAddress.port << std::endl;
} else {
std::cout << "Connecting via Secure Mode to "
<< socketAddress.address << ":" << socketAddress.port << std::endl;
std::cout << " RootCerts: " << rpcCredentials->rootCertsPem << std::endl;
std::cout << " SAN: " << rpcCredentials->subjectAlternativeName << std::endl;
// Note: privateKeyPem and certChainPem are typically used if the client
// needs to perform mutual TLS (mTLS).
}
// Cleanup: Release the credential memory once the RPC channel is established
if (rpcCredentials != nullptr) {
ASDVGateway_Client_deleteRpcCredentials(client, rpcCredentials);
}
Создание издателя и публикация сообщений
API ASDVGateway_Client_createPublication используется для регистрации сервисного блока издателя в SDV Gateway через интерфейс AIDL и создания очереди быстрых сообщений (FMQ) в процессе приложения. Binder используется только для настройки FMQ, но не для записи сообщений. Затем API ASDVGateway_Client_publishMessages используется для публикации сообщений в созданном издании. Для этого нужно записать данные в очередь FMQ издателя и отправить уведомление о том, что сообщения были записаны.
ASDVGateway_CreatePublicationParams_t params{
.serviceUnitName = "mirror-position-adjust-impl-1",
.unitType = ASDVGateway_UnitType_t{
.sdvPackageName = "android.sdv.samples.sdv_gateway",
.serviceBundleName = "DtPublisher",
.unitTypeName = "MirrorPositionAdjust",
},
.publisherUnitMetadata = ASDVGateway_PublisherUnitMetadata_t{
.version = 1,
.messageSizeBytes = 64,
.messageCount = 16,
},
};
ASDVGateway_PublicationMetadata_t metadata;
// 1. Create the Publication (allocates resources on the gateway)
ASDVGateway_StatusCode_t createStatus = ASDVGateway_Client_createPublication(
client,
¶ms,
&metadata,
&status
);
if (status.statusCode != ASDVGateway_StatusCode_OK) {
std::cerr << "Failed to create publication: " << status.errorMessage << std::endl;
return;
}
// 2. Publish Messages
// Note: serializedMessage should contain your encoded protobuf data
std::vector<uint8_t> serializedMessage;
ASDVGateway_Client_publishMessages(
client,
serializedMessage.data(),
serializedMessage.size(),
metadata.publicationId,
&status
);
Как создать подписчика с сервисом просмотра уведомлений
Чтобы подписаться на публикацию и получать уведомления Data Tunnel (например, о доступности сообщений), используйте ASDVGateway_Client_subscribeToPublicationByName API. Этот API также настраивает FMQ для чтения опубликованных сообщений. Вы можете настроить обратный вызов уведомлений во время оформления подписки или позже.
#include <map>
#include <memory>
#include <iostream>
class NativeSdvGatewayTestApp {
public:
struct SubscriptionContext {
int32_t subscriptionId;
std::string topicName;
// Add other context-specific data here (e.g., counters, buffers)
};
/**
* Callback triggered when new data is published to a subscribed topic.
*/
static void SubscriptionNotificationCallback(
const ASDVGateway_SubscriptionNotificationData_t* notification,
void* userData
) {
// The SDV Gateway client passes back the userData pointer.
// We ensure validity by managing the lifecycle of SubscriptionContext
// within the mSubscriptions map.
auto* ctx = reinterpret_cast<SubscriptionContext*>(userData);
if (ctx && notification) {
// React to data being available for the subscription.
// Example: handleIncomingData(notification->data, notification->size);
std::cout << "Notification received for sub ID: " << ctx->subscriptionId << std::endl;
}
}
private:
// Maps subscription handles/IDs to their respective contexts
std::map<int32_t, std::unique_ptr<SubscriptionContext>> mSubscriptions;
};
// --- Usage ---
NativeSdvGatewayTestApp app;
При оформлении подписки можно указать дополнительные параметры вместе с названием сервисного блока издателя. Эти параметры позволяют получить последнее опубликованное сообщение до подписки и задать минимальный интервал между уведомлениями.
ASDVGateway_SubscribeToPublicationByNameParams_t params{
.sdvVmName = "vm1",
.packageName = "test.package.impl.name",
.serviceBundleName = "TestBundleImpl",
.serviceUnitName = "GrpcServerImpl",
};
ASDVGateway_Subscriber_Options_t options{
// Ensure we receive the most recent message immediately upon subscribing
.flags = ASDVGATEWAY_SUBSCRIBER_OPTIONS_FLAG_FETCHLASTMESSAGE,
.minIntervalMs = 0, // No rate limiting; receive updates as they happen
};
// 1. Prepare the context for the callback
auto subCtx = std::make_unique<SubscriptionContext>();
ASDVGateway_PublicationMetadata_t metadata{};
// 2. Perform the subscription
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_subscribeToPublicationByName(
client,
¶ms,
&options,
NativeSdvGatewayTestApp::SubscriptionNotificationCallback,
static_cast<void*>(subCtx.get()), // Pass the raw pointer as userData
&metadata,
&status
);
// 3. Validation and Lifecycle Management
if (status.statusCode != ASDVGateway_StatusCode_OK) {
// Error handling: subCtx will be automatically deleted here
std::cerr << "Subscription failed: " << status.errorMessage << std::endl;
return;
}
// Store the context using the publicationId as the key.
// Once moved, the map owns the lifetime of subCtx.
app.mSubscriptions.emplace(metadata.publicationId, std::move(subCtx));
Чтобы прочитать сообщения из публикации, используйте API ASDVGateway_Client_readAvailableMessages:
uint32_t messagesAvailable = 0;
// 1. Check how many messages are waiting in the queue
ASDVGateway_StatusCode_t availStatus = ASDVGateway_Client_availableToRead(
client,
metadata.publicationId,
&messagesAvailable,
&status
);
if (status.statusCode != ASDVGateway_StatusCode_OK) {
// Error handling for availability check
return;
}
if (messagesAvailable == 0) {
// No messages available for this publication at this time
return;
}
// 2. Prepare the buffer
// metadata.messageSizeBytes was provided during the publication/subscription setup
std::vector<uint8_t> bytesForMessages;
bytesForMessages.resize(metadata.messageSizeBytes * messagesAvailable);
// 3. Read the messages from the Gateway into your local buffer
uint32_t actualMessageCount = 0; // Filled by the SDK with the number of messages read
ASDVGateway_StatusCode_t readStatus = ASDVGateway_Client_readAvailableMessages(
client,
metadata.publicationId,
bytesForMessages.data(),
bytesForMessages.size(),
&actualMessageCount,
&status
);
if (status.statusCode == ASDVGateway_StatusCode_OK) {
// Successfully read 'actualMessageCount' messages
// Process bytesForMessages...
}
Чтобы задать обратный вызов после подписки, используйте API ASDVGateway_Client_setNotificationCallbackForPublicationId:
ASDVGateway_StatusCode_t ASDVGateway_Client_setNotificationCallbackForPublicationId(
// [in] Opaque pointer to the client object.
const ASDVGateway_Client* client,
// [in] Identifies the specific publication to monitor.
const int32_t publicationId,
// [in] Function pointer triggered when new data is available
// for the specified publication.
ASDVGateway_SubscriptionNotificationCallback notificationCallback,
// [in] Optional user-defined context passed back to the callback.
void* notificationCallbackUserData,
// [out] Optional pointer to a status structure for result codes
// and error messages.
ASDVGateway_Status_t* outStatus
);
Как настроить сервис init
Сначала выполните инструкции из раздела Идентификация для встроенных сервисов, чтобы назначить идентификатор сервиса сервису через его идентификатор Android (AID).
Предположим, ваш сервис называется native_sdv_gateway_client_service, исполняемый файл находится в разделе /vendor по адресу /vendor/bin/native_sdv_gateway_client_service, а для запуска сервиса вы используете vendor_gateway_client в качестве идентификатора Android (AID).
После этого вы можете определить следующий сервис init:
service native_sdv_gateway_client_service /vendor/bin/native_sdv_gateway_client_service
class core
user vendor_gateway_client
group inet
disabled
oneshot
В этой конфигурации используются следующие параметры:
vendor_gateway_client– это идентификатор приложения, созданный или выбранный для сервиса.group inetтребуется для клиентов шлюза SDV, чтобы использовать сетевой интерфейс для связи между виртуальными машинами SDV. Другие группы можно добавить при необходимости.- В этом примере используются значения
disabledиoneshot. Возможно, вам нужно изменить варианты услуг. Начало предоставления услуги послеsdv_gateway.
Как создать правила SELinux для сервиса
Чтобы использовать SDV Gateway API, вам понадобятся следующие правила SELinux для сервиса:
# Define the domain for the service
type native_sdv_gateway_client_service, domain;
# Define the executable file type on the vendor partition
type native_sdv_gateway_client_service_exec, exec_type, file_type, vendor_file_type;
# Macro to transition from 'init' to this service's domain upon execution
init_daemon_domain(native_sdv_gateway_client_service)
# Macro to grant the necessary permissions to communicate with the SDV Gateway
sdv_gateway_client_domain(native_sdv_gateway_client_service)
В правилах SELinux:
init_daemon_domainпозволяет запускать сервис изinit.sdv_gateway_client_domainпредоставляет все необходимые разрешения SELinux для взаимодействия с шлюзом SDV. Следующая строка предоставляет эти правила исполняемому файлу:/vendor/bin/native_sdv_gateway_client_service u:object_r:native_sdv_gateway_client_service_exec:s0
Пример кода
Чтобы узнать больше о том, как запустить пример нативного кода, в котором показано, как документируются API C, перейдите на страницу system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/README.md.
Шлюз SDV в приложении IVI на Java
Модель взаимодействия для шлюза SDV на IVI показана на следующей диаграмме:
Рисунок 4. Шлюз SDV в приложении IVI Java.
В частности, модель:
- Все вызовы перенаправляются на уровень JNI.
- Связывает API Java и C.
- Определяет взаимодействия AIDL с
ISdvGateway:- Сообщения Init
- Как найти или создать RPC-сервер
- Создание темы и подписки Pub/Sub
- Выполнять взаимодействия с туннелем данных для обнаружения сервисов.
- Получать уведомления (например, о том, что данные доступны).
- Чтение и запись сообщений (FMQ для pub/sub) для создания пары ключей и сертификатов для TLS
Как включить клиентские библиотеки Gateway
Библиотека Java и оболочка JNI для libsdvgatewayclient C API устанавливаются в APEX на целевом устройстве IVI. Добавьте зависимость времени компиляции для заглушки библиотеки Java, библиотеки Java, которая должна использоваться во время выполнения, и необходимого APEX-пакета, содержащего библиотеку Java.
android_app {
name: "YourAppName",
// ...
static_libs: [
"libsdvgatewayclient-java",
],
libs: [
"libsdvgatewayclient-java-sdk.stubs",
],
uses_libs: [
"libsdvgatewayclient-java-sdk",
],
required: [
"com.sdv.google.gateway.client",
],
// ...
}
Если приложение, не входящее в пакет, создано вне дерева SDV, процесс будет похожим:
Скопируйте сгенерированный JAR-файл заглушки библиотеки Java и вспомогательный JAR-файл для RPC в папку libs приложения. Подробную информацию можно найти в статье
system/software_defined_vehicle/sdv_gateway/libsdvgatewayclient_apex/README.md.Добавьте JAR-файл заглушек в качестве зависимости только для компиляции. Например, обновите конфигурации Gradle, чтобы они зависели от заглушек, добавив запись
compileOnlyв разделdependencies:dependencies { // The library supporting functions for SDK RPC. // Statically linked into the app APK. implementation(files("libs/libsdvgatewayclient-java.jar")) // Stub of the SDV-Gateway client library. // Used only for compilation; the real implementation is provided // by the com.sdv.google.gateway.client APEX at runtime. compileOnly(files("libs/libsdvgatewayclient-java-sdk.jar")) }Добавьте библиотеку Java в раздел приложения файла
AndroidManifest.xml.<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <!-- Declares that this app requires the SDV Gateway client library. The 'android:required="true"' attribute ensures the app won't install/run if the library is missing from the system. --> <uses-library android:name="libsdvgatewayclient-java-sdk" android:required="true" /> </application> </manifest>
Загрузка библиотек
Создайте объект SdvGatewayClient (предоставляется клиентской библиотекой):
import google.sdv.gateway.client.SdvGatewayClient;
// --- Inside your Activity, Service, or ViewModel ---
// Initialize the SDV Gateway Client
SdvGatewayClient gatewayClient = new SdvGatewayClient();
Сообщения Init
Вызовите initComms(), используя название приложения в качестве названия пакета сервиса:
// Define a unique name for your service bundle (usually constant)
private static final String SERVICE_BUNDLE_NAME = "MySdvServiceBundle";
// ... inside an Activity or Service context ...
try {
// Initialize communications with the SDV Gateway
// context.getPackageName() provides "android.sdv.samples.gateway.client" or similar
gatewayClient.initComms(context.getPackageName(), SERVICE_BUNDLE_NAME);
Log.i("SDV_GATEWAY", "Communications initialized successfully");
} catch (Exception e) {
// Unlike the C API which uses status codes,
// the Java SDK often throws exceptions for initialization failures.
Log.e("SDV_GATEWAY", "Failed to initialize communications", e);
}
Обнаружение сервисов
Java API содержит методы для:
Получать уведомления о регистрации или отмене регистрации сервисных единиц определенного типа (или с определенным названием).
Перечислите текущие сервисные подразделения с определенным типом подразделения (или, как вариант, укажите название определенного типа сервисного подразделения). Чтобы получать уведомления об изменениях в сервисных единицах, создайте прослушиватель:
import google.sdv.gateway.client.ServiceUnitChangeListener;
import google.sdv.gateway.client.ServiceUnitChangeEventType;
import google.sdv.gateway.client.ServiceUnitDefinition;
// --- Implementation ---
ServiceUnitChangeListener listener = new ServiceUnitChangeListener() {
@Override
public void onServiceUnitChanged(
ServiceUnitChangeEventType eventType,
ServiceUnitDefinition serviceUnitDefinition
) {
// This is triggered when services matching your criteria
// are registered or unregistered on the vehicle network.
if (eventType == ServiceUnitChangeEventType.REGISTERED) {
// Handle new service discovery
} else if (eventType == ServiceUnitChangeEventType.UNREGISTERED) {
// Handle service removal
}
}
};
Метод Java addListenerForServiceUnitChangeByName уведомляет прослушиватель:
// 1. Register the listener for a specific service unit by name
AutoCloseable handle = gatewayClient.addListenerForServiceUnitChangeByName(
new UnitNameDiscoveryArgs(
"", // sdvVmName (empty for local/auto-discovery)
"android.sdv.samples.cluster", // sdvPackageName
"ClusterServer", // serviceBundleName
"android-sdv-samples-cluster-cluster" // serviceUnitName
),
listener
);
// --- Later in the application lifecycle ---
// 2. To stop receiving notifications and clean up resources, close the handle.
try {
if (handle != null) {
handle.close();
}
} catch (Exception e) {
Log.e("SDV_GATEWAY", "Error while closing the listener handle", e);
}
Также можно использовать метод Java addListenerForServiceUnitChangeByType, чтобы уведомлять прослушиватель, когда регистрируются или отменяются сервисы с указанным типом единицы:
// 1. Register a listener based on the Service Unit Type
AutoCloseable handle = gatewayClient.addListenerForServiceUnitChangeByType(
new UnitType(
"com.android.testapp.sdvcarmonitor", // sdvPackageName
"SunroofRpcServer", // serviceBundleName
"Sunroof" // unitTypeName
),
listener
);
// --- Execution Loop / Lifecycle ---
// 2. To stop receiving notifications and clean up memory, close the handle.
// This effectively unregisters the listener from the SDV Gateway.
try {
if (handle != null) {
handle.close();
}
} catch (Exception e) {
Log.e("SDV_GATEWAY", "Failed to close the service unit listener handle", e);
}
Для addListenerForServiceUnitChangeByName и addListenerForServiceUnitChangeByType после добавления прослушиватель получает уведомления обо всех зарегистрированных сервисных единицах. Чтобы получить только зарегистрированные сервисные единицы по названию или типу, используйте Java API listServiceUnitsByName и listServiceUnitsByType:
import google.sdv.gateway.client.ServiceUnitDefinition;
import google.sdv.gateway.client.UnitNameDiscoveryArgs;
import google.sdv.gateway.client.UnitType;
// --- 1. Synchronous Lookup by Specific Name ---
ServiceUnitDefinition[] definitionsByName = gatewayClient.listServiceUnitsByName(
new UnitNameDiscoveryArgs(
"", // sdvVmName
"android.sdv.samples.cluster", // sdvPackageName
"ClusterServer", // serviceBundleName
"android-sdv-samples-cluster-cluster" // serviceUnitName
)
);
// --- 2. Synchronous Lookup by Service Type ---
ServiceUnitDefinition[] definitionsByType = gatewayClient.listServiceUnitsByType(
new UnitType(
"com.android.testapp.sdvcarmonitor", // sdvPackageName
"SunroofRpcServer", // serviceBundleName
"Sunroof" // unitTypeName
)
);
// Example processing
if (definitionsByType.length > 0) {
ServiceUnitDefinition firstSunroof = definitionsByType[0];
// Proceed to connect...
}
Поток RPC-сервера
Клиенты SDV Gateway в информационно-развлекательных системах IVI используют для связи с сервисами SDV вызов удаленных процедур Google (gRPC). Эти взаимодействия основаны на определениях протоколов из каталога VSIDL, которые совпадают с определениями, используемыми в SDV Core, или похожи на них. Для приложений Java используется gRPC-Java. Для сервера приложений предоставляется пример определения протокола сервера sunroof.proto.
service Sunroof {
/**
* Retrieves the current state of the sunroof (e.g., position, tilt, status).
*
* @param .google.protobuf.Empty - No input parameters required.
* @return SunroofStateResponse - The current telemetry data for the sunroof.
*/
rpc GetSunroofState(.google.protobuf.Empty) returns (SunroofStateResponse) {}
}
Установите связь с соответствующей библиотекой proto и определите сервис:
import com.android.sdv.sdvgrpclibrary.SunroofGrpc;
import com.android.sdv.sdvgrpclibrary.SunroofStateResponse; // Assuming this is the generated class
import com.google.protobuf.Empty;
import io.grpc.stub.StreamObserver;
/**
* Implementation of the Sunroof gRPC service.
* This class handles the logic for the RPCs defined in your .proto file.
*/
static class SunroofGrpcImpl extends SunroofGrpc.SunroofImplBase {
@Override
public void getSunroofState(Empty request, StreamObserver<SunroofStateResponse> responseObserver) {
// 1. Fetch current sunroof data (e.g., from a Hardware Abstraction Layer)
int currentPosition = 50; // Example value: 50% open
// 2. Build the Protobuf response message
SunroofStateResponse response = SunroofStateResponse.newBuilder()
.setPercentageOpen(currentPosition)
.build();
// 3. Send the response to the client using the observer
responseObserver.onNext(response);
// 4. Close the stream to signal that the RPC is finished
responseObserver.onCompleted();
}
}
Зарегистрируйте сервер gRPC с учетными данными безопасного и небезопасного канала:
// 1. Define the type signature for the service
UnitType unitType = new UnitType(
"com.android.testapp.sdvcarmonitor", // sdvPackageName
"SunroofRpcServer", // serviceBundleName
"Sunroof" // typeName (Unit Type)
);
// 2. Register the RPC server with the Gateway
// The gateway creates a mapping between the ServiceUnitName and your implementation.
server = gatewayClient.registerRpcServer(
"SunroofRpcServerImpl-1", // serviceUnitName (Unique instance name)
unitType, // unitType defined earlier
"SUNROOF_GRPC_SERVER_VALUE_HOLDER".getBytes(), // appMetadataValueHolder (Static discovery data)
1, // appMetadataVersion
new SunroofGrpcImpl() // The actual gRPC service implementation
);
Клиентская библиотека создает объект сервера gRPC, SdvGatewayClient.java, и обрабатывает обновления корневых сертификатов:
// 1. Initialize credentials (Insecure for dev, TLS for production)
ServerCredentials serverCredentials = InsecureServerCredentials.create();
// 2. Build and start the OkHttp-based gRPC server
final int bindAnyPort = 0;
final Server server = OkHttpServerBuilder
.forPort(bindAnyPort, serverCredentials)
.addService(gRpcServerImplementation) // Your SunroofGrpcImpl
.build()
.start();
// The assigned port can now be retrieved using server.getPort()
int actualPort = server.getPort();
// 3. Prepare the JNI data structure
// This object mirrors the ASDVGateway_ServiceUnitDefinition_t C struct
JniServiceUnitDefinition definition = new JniServiceUnitDefinition();
// Fill the RPC service definition params (Port, Name, Type, etc.)
definition.setPort(actualPort);
definition.setServiceUnitName("SunroofRpcServerImpl-1");
// 4. Perform the cross-language call
// This jumps from Java -> JNI -> ASDVGateway_Client_registerRpcServer (C API)
mJniClient.nativeRegisterRpcServer(definition);
Путь клиента RPC
В этом примере кода приведено определение протокола сервера (tpms.proto) для сервера, к которому приложение подключается как клиент:
/**
* The TPMS service provides real-time pressure and temperature
* data for all tires on the vehicle.
*/
service Tpms {
/**
* Returns the full state of all monitored tires.
*/
rpc GetTpmsState(.google.protobuf.Empty) returns (TpmsStateResponse) {}
/**
* A filtered query that returns only the tires
* below the recommended pressure threshold.
*/
rpc GetLowTires(.google.protobuf.Empty) returns (LowTiresResponse) {}
}
Установите связь с соответствующей библиотекой прототипов:
import com.android.sdv.sdvgrpclibrary.TpmsGrpc;
import io.grpc.ManagedChannel;
// --- Inside your Client Application ---
// 1. Request a ManagedChannel from the Gateway for a specific service unit
ManagedChannel managedChannel = gatewayClient.connectToRpcServerByName(
"", // sdvVmName (empty for local/auto-lookup)
"android.sdv.samples.cluster", // packageName
"ClusterServer", // serviceBundleName
"android-sdv-samples-cluster-cluster" // serviceUnitName
);
// 2. Use the channel to create a gRPC stub (e.g., for the TPMS service)
TpmsGrpc.TpmsBlockingStub tpmsStub = TpmsGrpc.newBlockingStub(managedChannel);
// 3. Now you can call RPC methods directly
// TpmsStateResponse response = tpmsStub.getTpmsState(Empty.getDefaultInstance());
Для поиска сервера RPC вызывается API ASDVGateway_Client_findRpcServerByName. Если сервер RPC найден, управляемый канал создается в незащищенном режиме или настраивается на использование конфигурации TLS, как и в случае с сервером RPC, в зависимости от конфигурации обнаружения сервисов. Приложение создает заглушки с помощью объекта ManagedChannel и вызывает методы сервера:
import com.android.sdv.sdvgrpclibrary.TpmsGrpc;
import com.android.sdv.sdvgrpclibrary.TpmsStateResponse;
import com.google.protobuf.Empty;
import io.grpc.stub.MetadataUtils;
// 1. Create a "Blocking Stub" from the existing ManagedChannel.
// We apply an interceptor to attach mandatory metadata (headers)
// required by the SDV Gateway for authorization.
TpmsGrpc.TpmsBlockingStub tpmsStub = TpmsGrpc.newBlockingStub(managedChannel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(mMetadata));
// 2. Execute the RPC call.
// Because this is a "BlockingStub," the thread will wait here until
// the vehicle service responds or times out.
TpmsStateResponse tpmsStateResponse = tpmsStub.getTpmsState(Empty.getDefaultInstance());
// 3. Extract the domain-specific state object from the Protobuf response.
// 'newState' can now be used to update your UI or application logic.
newState = tpmsStateResponse.getTpmsState();
Как изменить сеть по умолчанию для процесса
Возможно, вам потребуется переключиться с VLAN SDV-RPC в качестве сети процесса по умолчанию
для открытия сокетов для подключений, предназначенных для интернета. Вызовите функции unbindProcessFromSdvRpcNetworkInterface и bindProcessToSdvRpcNetworkInterface, чтобы отменить привязку процесса и снова привязать его к VLAN SDV-RPC. Оба вызова работают как глобальные переключатели, изменяя сетевой интерфейс, с которым сокеты связаны для всех потоков процесса.
// 1. Redirect all socket traffic from this process to the SDV-RPC VLAN.
// This call is required for making RPC calls or hosting RPC services for SDV.
gatewayClient.bindProcessToSdvRpcNetworkInterface();
// --- Process is now communicating over the SDV-RPC interface ---
// 2. Revert the process network binding back to the "default" interface.
// This allows the app to access internet resources again.
gatewayClient.unbindProcessFromSdvRpcNetworkInterface();
Создание издателя и публикация сообщений
// 1. Define the interface and type for the publication
UnitType unitType = new UnitType(
"android.sdv.samples.tires.interface", // sdvPackageName
"TirePressurePublisherInterface", // serviceBundleName
"TirePressure" // typeName
);
// 2. Configure the Publisher's buffer and message constraints
PublisherUnitMetadata publisherUnitMetadata = new PublisherUnitMetadata(
1, // version
64, // message size in bytes (fixed size for performance)
128 // max message count (buffer depth)
);
// 3. Create the Publication instance through the Gateway
Publisher tirePublisher = gatewayClient.createPublication(
"tire-pressure-service-unit-name",
unitType,
publisherUnitMetadata
);
// 4. Prepare and publish a message
// In a real app, you would encode your sensor data into this byte array
byte[] msg = new byte[publisherUnitMetadata.messageSizeBytes];
// ... fill msg with data ...
tirePublisher.publish(msg);
Внутренне API Java createPublication и publish опираются на собственные API ASDVGateway_Client_createPublication и ASDVGateway_Client_readAvailableMessages. Подробную информацию о C API можно найти в разделе Использование шлюза SDV в собственном сервисе IVI. Объект Publisher предоставляет контекст для написания сообщений и управления жизненным циклом публикации.
Создание подписчика с сервисом просмотра уведомлений
Java API позволяет передавать прослушиватель в качестве параметра методу подписки на публикацию и возвращает объект Subscription.
Listenerполучает уведомление, когда для публикации, на которую он подписан, появляются данные.Subscriptionдействует как объект контекста и может использоваться для чтения сообщений и закрытия подписки.
// 1. Define the Listener to handle incoming data notifications
SubscriptionNotificationListener listener = new SubscriptionNotificationListener() {
@Override
public void onSubscriptionNotification(
Subscription subscription,
SubscriptionNotificationType notificationType
) {
// Only process if the notification indicates new data is ready
if (notificationType != SubscriptionNotificationType.DataAvailable) {
return;
}
// Read the message from the subscription buffer
byte[] content = subscription.readMessage();
// Note: This callback often runs on a background thread provided by the SDK.
// If you need to update the UI, use a Handler or View.post().
processTireData(content);
}
};
// 2. Subscribe to the publication by its unique name
Subscription tireSubscription = gatewayClient.subscribeToPublicationByName(
"", // sdvVmName (empty for auto-lookup)
"android.sdv.samples.dt_publisher", // packageName
"SdvGatewayDtPublisher", // serviceBundleName
"tire", // serviceUnitName
listener
);
// 3. Read Messages
// You can poll or register callbacks for new messages.
// When polling the message, call this method in a loop.
// When registering callbacks, call this method to get the message payload.
byte[] manualContent = tireSubscription.readMessage();
Пример кода
Обратные вызовы прослушивателя вызываются в одном потоке, управляемом клиентом, чтобы минимизировать обработку в прослушивателях и предотвратить задержки в получении уведомлений для других подписок. Уровень Java использует C API для управления подписками, обработки уведомлений и получения сообщений. Пример использования API можно найти в приложении на Java, которое содержится в двоичном файле по адресу system/software_defined_vehicle/samples/sdv_gateway/README.md.
Требуемые права доступа
Для вызова клиентского API SDV Gateway не требуются специальные разрешения, но для приложений необходимо применить правильные правила SELinux.
- Для подписок и публикаций в туннеле данных разрешения не требуются.
- Для SDV RPC требуются следующие разрешения:
android.permission.INTERNETandroid.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
Для android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS вам также понадобится файл белого списка в разделе etc/permissions на том же разделе, что и ваше приложение. Например, для SdvCarMonitorTestApp (название пакета com.android.testapp.sdvcarmonitor) файл будет выглядеть так:
<?xml version="1.0" encoding="utf-8"?>
<permissions>
<privapp-permissions package="com.android.testapp.sdvcarmonitor">
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
</privapp-permissions>
</permissions>
Правила SELinux
Чтобы использовать SDV Gateway API, приложениям Java требуются те же разрешения, что и встроенным сервисам. Предоставьте эти разрешения с помощью макроса SELinux sdv_gateway_client_domain():
sdv_gateway_client_domain(my_oem_sdv_gateway_client_app)
Производитель оборудования определяет домен my_oem_sdv_gateway_client_app для приложений Java, которым разрешено использовать шлюз SDV. Используйте SDV Gateway только в системных и привилегированных приложениях.
Местоположения кода
Получите исходный код шлюза SDV по адресу
system/software_defined_vehicle/sdv_gateway/. Вы можете получить образцы шлюза SDV для:
- API клиента C:
system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/ - Клиентский API Java:
system/software_defined_vehicle/samples/sdv_gateway/SdvCarMonitorTestApp/