Шлюз программно-определяемого транспортного средства (SDV) на системах информационно-развлекательной системы автомобиля (IVI) обеспечивает связь между системами IVI и удаленными сервисами SDV. Шлюз позволяет Java-приложениям OEM-производителей и собственным сервисам, таким как VHAL, взаимодействовать с сервисами SDV. Шлюз использует устоявшиеся методы связи, включая регистрацию сервисов, обнаружение и RPC.
Этот шлюз отвечает определенным требованиям проекта AAOS SDV, таким как обеспечение возможности использования эталонной реализации VHAL с помощью туннеля данных SDV для передачи информации о свойствах. Он также позволяет приложениям Android на Java и Kotlin использовать стек связи SDV, регистрироваться в качестве служб, находить другие службы SDV и взаимодействовать с ними.
Расположение кода SDV Gateway и примеры клиентских приложений SDV Gateway см. в разделе «Расположение кода» .
модели интеграции шлюза SDV
Используйте связь SDV-IVI через шлюз SDV в архитектуре IVI.
Шлюз SDV взаимодействует с Java-приложениями, собственными сервисами, стеком связи SDV и автомобильной сетью. На рисунке 1 показано это взаимодействие:
Рисунок 1. Взаимодействие шлюза SDV.
На данной системной схеме:
- Приложения взаимодействуют с шлюзом SDV через его клиентские службы.
- AAOS SDV SDK предоставляет вам:
- API ISdvGateway AIDL для межпроцессного взаимодействия.
- Библиотеки связи для сетевого взаимодействия.
- Удобный 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.
Используйте SDV Gateway в собственном сервисе IVI.
На рисунке 3 показано использование шлюза SDV на IVI:
Рисунок 3. Шлюз SDV на IVI.
Предварительные условия
Запустите пул потоков Binder:
Для работы клиентской библиотеки SDV Gateway требуется запущенный пул потоков Binder, чтобы получать асинхронные обратные вызовы от служб Binder.
API, необходимый для создания клиента SDV Gateway, завершается с ошибкой, если пул потоков Binder не запущен.
Включить собственную клиентскую библиотеку шлюза.
Библиотека Native Gateway Client предоставляет C API. Добавьте экземпляр libsdvgatewayclient в качестве зависимости, чтобы использовать C API:
cc_binary {
name: "your_binary_name",
srcs: ["main.cpp"],
shared_libs: [
"libsdvgatewayclient",
],
}
Загрузите собственный клиент шлюза.
#include "libsdvgatewayclient.h"
Создайте экземпляр нативного клиента.
ASDVGateway_Client* client;
ASDVGateway_StatusCode_t statusCode = ASDVGateway_Client_new(
&client, /*outStatus*/nullptr);
После создания клиента, клиент:
Сохраняет состояние для всех последующих взаимодействий со службой шлюза.
Выступает в качестве контекста для всех взаимодействий с другими сервисами, поддерживающими SDV, и передается в качестве первого параметра функциям C API.
Коды состояния и сообщения об ошибках
Большинство функций C API имеют такое определение:
ASDVGateway_StatusCode_t ApiFunctionName(..., ASDVGateway_Status_t* outStatus);
Для оценки успешности выполнения можно проверить возвращаемый код состояния, который имеет тип ASDVGateway_StatusCode_t . Также можно передать указатель на структуру, в которую функция может заполнить код состояния и сообщение об ошибке. Указатель передается в качестве последнего параметра, который называется outStatus . Значение null означает, что выходная структура не используется.
Вызывающая сторона должна выделить в памяти структуру состояния для сообщения об ошибке. Структура состояния может содержать как код состояния, так и сообщение об ошибке. Пример получения сообщения об ошибке при создании нового клиента доступен по ссылке.
Для оценки успеха:
Проверьте возвращенный код состояния, который имеет тип
ASDVGateway_StatusCode_t.Передайте указатель на структуру, в которую функция может заполнить код состояния и сообщение об ошибке.
- В качестве последнего параметра передается указатель с именем
outStatus. - Значение null означает, что выходная структура не используется.
- Вызывающая сторона должна выделить структуру состояния памяти для сообщения об ошибке.
- Структура состояния может содержать код состояния и сообщение об ошибке.
- В качестве последнего параметра передается указатель с именем
Вот пример, демонстрирующий, как получить сообщение об ошибке для нового клиента:
#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 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
Клиентская библиотека Gateway обрабатывает настройки протокола Transport Layer Security (TLS) для связи с другими приложениями, поддерживающими SDV. В частности, она получает необходимые для связи настройки TLS. Вот как определяется использование TLS:
TLS используется, когда режим загрузки SDV
LOCKED.Небезопасная связь используется, когда режим загрузки SDV
UNLOCKED. При использовании TLS клиентская библиотека шлюза генерирует пару ключей, известную только процессу приложения. Клиент получает учетные данные RPC для создания канала RPC-сервера или RPC-клиента. RPC использует выделенную VLAN SDV-RPC. Клиентская библиотека шлюза вызывает функцию 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 через его интерфейс 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 (например, о доступности сообщений) используйте API ASDVGateway_Client_subscribeToPublicationByName . Этот 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
);
Настройка службы инициализации
Предположим, ваша служба называется native_sdv_gateway_client_service , исполняемый файл находится по адресу /vendor/bin/native_sdv_gateway_client_service , и вы используете vendor_sdv_services в качестве Android UID (AID) для запуска службы.
Ни одна нативная служба не должна использовать AID для приложений Android. Здесь необходимо использовать AID из зарезервированного диапазона для служб поставщиков. При такой настройке вы можете определить следующую службу инициализации :
service native_sdv_gateway_client_service /vendor/bin/native_sdv_gateway_client_service
class core
user vendor_sdv_services
group inet
disabled
oneshot
Где
-
vendor_sdv_services— это AID, созданный или выбранный для службы. -
group inetнеобходима для клиентов шлюза SDV, чтобы использовать сетевой интерфейс для связи между виртуальными машинами SDV. При необходимости можно добавить и другие группы. - В этом примере используются
disabledиoneshot. Возможно, вам потребуется скорректировать параметры службы для вашей системы. Запустите службу послеsdv_gateway.
Создайте правила SELinux для службы.
Для использования API шлюза SDV вам потребуются следующие правила 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
Пример кода
Чтобы узнать больше о запуске примера нативного кода, демонстрирующего документацию по C API, см. system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/README.md .
SDV Gateway в Java-приложении IVI
На данной диаграмме представлена модель взаимодействия шлюза SDV на платформе IVI:
Рисунок 4. Шлюз SDV в Java-приложении IVI.
В частности, модель:
- Направляет все вызовы на уровень JNI.
- Связывает Java и C API.
- Определяет взаимодействие AIDL с
ISdvGateway:- Инициирование связи
- Найдите или создайте RPC-сервер.
- Создать публикацию/подписку
- Выполнение обнаружения сервисов. Взаимодействие через туннели данных.
- Получайте уведомления (например, о наличии данных).
- Чтение и запись сообщений (FMQ для модели публикации/подписки) для создания пар ключей и сертификатов для TLS.
Включить клиентские библиотеки шлюза
Библиотека Java и JNI-обертка для C API libsdvgatewayclient установлены в 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 в раздел `app` файла `
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();
Инициирование связи
Вызовите функцию 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);
}
обнаружение сервисов
API Java содержит методы для:
Получайте уведомления о регистрации или снятии с регистрации сервисных единиц определенного типа (или с определенным названием).
Отобразить список текущих сервисных подразделений с определенным типом (или, альтернативно, сопоставить с конкретным именем типа сервисного подразделения). Чтобы получать уведомления об изменениях сервисных подразделений, создайте слушатель:
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 используют удаленный вызов процедур Google (gRPC) для связи со службами SDV. Эти взаимодействия основаны на определениях протоколов из каталога 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) {}
}
Свяжите проект с соответствующей библиотекой прототипов и определите сервис:
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) {}
}
Свяжите проект с соответствующей библиотекой proto:
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());
Внутри системы вызывается API-функция ASDVGateway_Client_findRpcServerByName для поиска RPC-сервера. Если 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);
Внутри Java API-интерфейсы createPublication и publish используют собственные API-интерфейсы ASDVGateway_Client_createPublication и ASDVGateway_Client_readAvailableMessages . Подробную информацию об API на языке C см. в разделе «Использование SDV Gateway в собственном сервисе IVI» . Объект Publisher предоставляет контекст для записи сообщений и управления жизненным циклом публикации.
Создайте подписчика с обработчиком уведомлений.
API Java позволяет передавать слушатель в качестве параметра методу подписки на публикацию, и он возвращает объект 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 не требуются специальные разрешения, но необходимо применить соответствующие правила SELinux для ваших приложений.
- Для подписки на Data Tunnel и публикаций никаких разрешений не требуется.
- Для работы SDV RPC необходимы следующие права доступа:
-
android.permission.INTERNET -
android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
-
For android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS , you also need an allowlist file under etc/permissions in the same partition as your app, for example, for the SdvCarMonitorTestApp (package name com.android.testapp.sdvcarmonitor ), the file looks like:
<?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
Для использования API шлюза SDV приложениям 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 только из системных и привилегированных приложений.
Места расположения кодов
Исходный код SDV Gateway можно получить по адресу system/software_defined_vehicle/sdv_gateway/ . Примеры работы SDV Gateway доступны для следующих приложений:
- Клиентский C API:
system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/ - Клиентский Java API:
system/software_defined_vehicle/samples/sdv_gateway/SdvCarMonitorTestApp/