از دروازه SDV در IVI استفاده کنید

درگاه خودرو مبتنی بر نرم‌افزار (SDV) در سیستم‌های سرگرمی درون خودرو (IVI) ارتباط بین سیستم‌های IVI و سرویس‌های SDV از راه دور را تسهیل می‌کند. این درگاه به برنامه‌های جاوا OEM و سرویس‌های بومی، مانند VHAL، اجازه می‌دهد تا با سرویس‌های SDV تعامل داشته باشند. این درگاه از روش‌های ارتباطی تثبیت‌شده، از جمله ثبت سرویس، کشف سرویس و RPC استفاده می‌کند.

این دروازه، الزامات خاص پروژه AAOS SDV، مانند فعال‌سازی پیاده‌سازی مرجع VHAL با استفاده از SDV Data Tunnel برای اطلاعات ملک را برآورده می‌کند. همچنین به برنامه‌های اندروید جاوا و کاتلین در IVI اجازه می‌دهد تا از پشته SDV Comms استفاده کنند، به عنوان سرویس ثبت شوند، سایر سرویس‌های SDV را پیدا کنند و با آنها ارتباط برقرار کنند.

برای مکان‌های کد SDV Gateway و نمونه‌های کلاینت‌های SDV Gateway، به مکان‌های کد مراجعه کنید.

مدل‌های یکپارچه‌سازی SDV Gateway

استفاده از ارتباطات SDV-IVI از طریق درگاه SDV در معماری IVI

SDV Gateway با برنامه‌های جاوا، سرویس‌های بومی، پشته ارتباطات SDV و شبکه خودرو تعامل دارد. شکل 1 این تعاملات را نشان می‌دهد:

تعاملات SDV Gateway

شکل 1. تعاملات SDV Gateway.

در این نمودار سیستم:

  • برنامه‌ها از طریق سرویس‌های کلاینت SDV Gateway با آن تعامل دارند.
  • کیت توسعه نرم‌افزار AAOS SDV موارد زیر را در اختیار شما قرار می‌دهد:
    • رابط برنامه‌نویسی کاربردی ISdvGateway AIDL برای ارتباط بین پردازشی.
    • کتابخانه‌های ارتباطی برای تعامل شبکه.
    • رابط برنامه‌نویسی کاربردی C برای یکپارچه‌سازی سرویس‌های بومی.
  • رابط برنامه‌نویسی کاربردی هوش مصنوعی ISdvGateway توسط سرویس و زیرسیستم SDV Gateway پیاده‌سازی شده است.
  • سرویس SDV Gateway موارد زیر را مدیریت می‌کند:
    • کشف سرویس دو طرفه
    • ارتباط با سرویس‌های SDV از راه دور.
    • منطق اصلی کسب و کار.
  • زیرسیستم SDV Gateway به شبکه خودرویی متصل می‌شود.
  • سرویس‌های بومی، از جمله پیاده‌سازی VHAL، می‌توانند مستقیماً یا از طریق API C مربوط به SDK از ISdvGateway AIDL API استفاده کنند.
  • پروکسی VHAL به عنوان یک پیاده‌سازی مرجع VHAL عمل می‌کند که شامل ادغام نگاشت VSIDL است.

مدل یکپارچه‌سازی برای SDV Gateway روی یک سرویس بومی IVI

مدل ادغام در شکل 2 نشان داده شده است:

مدل یکپارچه‌سازی SDV Gateway

شکل ۲. مدل یکپارچه‌سازی SDV Gateway.

استفاده از SDV Gateway روی یک سرویس بومی IVI

شکل 3 نحوه‌ی استفاده از SDV Gateway در IVI را نشان می‌دهد:

درگاه SDV روی IVI

شکل ۳. درگاه SDV روی IVI.

پیش‌شرط‌ها

شروع استخر نخ Binder:

  • کتابخانه کلاینت SDV Gateway برای دریافت فراخوانی‌های ناهمزمان از سرویس‌های Binder، به یک مخزن نخ Binder آغاز شده نیاز دارد.

  • API مورد نیاز برای ایجاد یک کلاینت SDV Gateway زمانی که یک مخزن نخ Binder شروع نشده باشد، با شکست مواجه می‌شود.

کتابخانه کلاینت محلی Gateway را اضافه کنید

کتابخانه Native Gateway Client یک API زبان C ارائه می‌دهد. برای استفاده از API زبان C، یک نمونه از libsdvgatewayclient به عنوان وابستگی اضافه کنید:

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);

پس از ایجاد کلاینت، کلاینت:

  • وضعیت (state) را برای تمام تعاملات بعدی با سرویس Gateway نگه می‌دارد.

  • به عنوان زمینه تمام تعاملات با سایر سرویس‌های دارای قابلیت SDV عمل می‌کند و به عنوان اولین پارامترها به توابع API زبان C ارسال می‌شود.

کدهای وضعیت و پیام‌های خطا

اکثر توابع API زبان C این تعریف را دارند:

ASDVGateway_StatusCode_t ApiFunctionName(..., ASDVGateway_Status_t* outStatus);

برای ارزیابی موفقیت، می‌توانید کد وضعیت برگشتی را که از نوع ASDVGateway_StatusCode_t است، بررسی کنید. همچنین می‌توانید یک اشاره‌گر به ساختاری که تابع می‌تواند کد وضعیت و یک پیام خطا را در آن جای دهد، ارسال کنید. اشاره‌گر به عنوان آخرین پارامتر با نام outStatus ارسال می‌شود. مقدار null به این معنی است که از ساختار خروجی استفاده نمی‌شود.

فراخواننده باید ساختار وضعیت حافظه را برای پیام خطا اختصاص دهد. ساختار وضعیت می‌تواند هم کد وضعیت و هم یک پیام خطا را در خود نگه دارد. نمونه‌ای از بازیابی پیام خطا هنگام ایجاد یک کلاینت جدید در دسترس است.

برای ارزیابی موفقیت:

  1. کد وضعیت برگشتی را که از نوع ASDVGateway_StatusCode_t است، بررسی کنید.

  2. یک اشاره‌گر به ساختاری ارسال کنید که تابع بتواند کد وضعیت و پیام خطا را در آن وارد کند.

    • اشاره‌گر به عنوان آخرین پارامتر با نام 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 (شامل کاراکتر پایان null \0 ) پر می‌شود. اگر خطایی رخ ندهد (کد وضعیت OK باشد)، پیام خطا یک رشته خالی است.

ارتباطات اولیه

دستورات اولیه (Init comms) ارتباط بین برنامه تماس گیرنده و سایر برنامه‌ها را با استفاده از SDV Comm Stack و SDV Gateway مقداردهی اولیه می‌کنند. دستورات اولیه را می‌توان در هر دو زمینه زیر فراخوانی کرد:

  • بعد از اینکه کلاینت ایجاد شد.

  • قبل از هرگونه تعامل تونل داده، 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, &params, &status);

// Recommended check
if (statusCode != ASDVGateway_StatusCode_OK) {
    std::cerr << "Failed to init comms: " << status.errorMessage << std::endl;
}

کشف سرویس

شما می‌توانید وقتی واحدهای خدماتی از یک نوع یا نام خاص ثبت یا ثبت نشده‌اند، اعلان دریافت کنید. تابع فراخوانی در یک نخ متعلق به کلاینت دروازه اطلاع‌رسانی می‌شود. این فراخوانی در ابتدا با تمام واحدهای خدماتی که درست پس از فراخوانی API C ثبت شده‌اند، آغاز می‌شود. پس از آغاز اولیه، اعلان‌ها با به‌روزرسانی‌ها ادامه می‌یابند تا زمانی که شنونده به صراحت ثبت نشده باشد.

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
);

فراخوانی برگشتی (callback) به صورت همزمان در thread فراخوانی کننده در طول فراخوانی ASDVGateway_Client_fetchServiceUnitsByType API آغاز می‌شود. از 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 تنظیمات امنیت لایه انتقال (TLS) را برای ارتباط با سایر برنامه‌های دارای SDV مدیریت می‌کند. به طور خاص، تنظیمات TLS مورد نیاز برای ارتباط را بازیابی می‌کند. در اینجا نحوه تعیین میزان استفاده از TLS آمده است:

  • TLS زمانی استفاده می‌شود که حالت بوت SDV LOCKED باشد.

  • ارتباط ناامن زمانی استفاده می‌شود که حالت بوت SDV UNLOCKED باشد. وقتی از TLS استفاده می‌شود، کتابخانه کلاینت Gateway یک جفت کلید تولید می‌کند که فقط برای فرآیند برنامه شناخته شده است. کلاینت اعتبارنامه‌های RPC را برای ایجاد یک سرور RPC یا یک کانال کلاینت RPC بازیابی می‌کند. RPC از یک SDV-RPC VLAN اختصاصی استفاده می‌کند. کتابخانه کلاینت Gateway، android_setprocnetwork را فراخوانی می‌کند تا شبکه پیش‌فرض فرآیند را به SDV-RPC VLAN، چه در حین یا بعد از فراخوانی ASDVGateway_Client_initComms ، تغییر دهد.

در دسترس بودن RPC

کلاینتی که برای شروع در بوت اولیه پیکربندی شده است، ممکن است قبل از در دسترس بودن SDV-RPC VLAN شروع به کار کند. قبل از تلاش برای ایجاد هرگونه سرور 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 و هم اتصال فرآیند به SDV-RPC VLAN را بررسی می‌کند.

شبکه پیش‌فرض فرآیند را تغییر دهید

ممکن است لازم باشد از SDV-RPC VLAN به عنوان شبکه پیش‌فرض فرآیند تغییر دهید تا سوکت‌ها را برای اتصالات متصل به اینترنت باز کنید. برای جدا کردن و اتصال مجدد فرآیند به SDV-RPC VLAN ASDVGateway_Client_unbindProcessFromSdvRpcNetworkInterface و ASDVGateway_Client_bindProcessToSdvRpcNetworkInterface را فراخوانی کنید. این دو فراخوانی مانند سوئیچ‌های سراسری عمل می‌کنند و رابط شبکه‌ای را که سوکت‌ها برای همه نخ‌های فرآیند به آن متصل هستند، تغییر می‌دهند.

// 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,
    &params,
    &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,
    &params,
    &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,
    &params,
    &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
);

ایجاد یک مشترک با شنونده اعلان

برای عضویت در یک انتشار و دریافت اعلان‌های تونل داده (مانند در دسترس بودن پیام)، از 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,
    &params,
    &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

ابتدا، برای اختصاص هویت سرویس به سرویس از طریق شناسه اندروید (AID) آن، از بخش Identity برای سرویس‌های بومی استفاده کنید.

فرض کنید سرویس شما native_sdv_gateway_client_service نام دارد، فایل اجرایی در پارتیشن /vendor در /vendor/bin/native_sdv_gateway_client_service قرار دارد و شما vendor_gateway_client به عنوان شناسه اندروید (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 شناسه‌ی کاربری (AID) ایجاد شده یا انتخاب شده برای سرویس است.
  • برای استفاده کلاینت‌های SDV Gateway از رابط شبکه برای برقراری ارتباط بین ماشین‌های مجازی SDV، group inet مورد نیاز است. در صورت نیاز می‌توان گروه‌های دیگری را نیز اضافه کرد.
  • این مثال disabled و oneshot استفاده می‌کند. ممکن است لازم باشد گزینه‌های سرویس را برای سرویس خود تنظیم کنید. سرویس را پس از sdv_gateway شروع کنید.

ایجاد قوانین SELinux برای سرویس

برای استفاده از APIهای SDV Gateway، به قوانین 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 Gateway فراهم می‌کند. خط زیر این قوانین را به فایل اجرایی اعطا می‌کند:

    /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 Gateway روی برنامه جاوا IVI

مدل تعامل برای یک SDV Gateway در IVI در این نمودار نشان داده شده است:

SDV Gateway روی برنامه جاوا IVI

شکل ۴. درگاه SDV روی برنامه جاوا IVI.

به طور خاص، مدل:

  • تمام فراخوانی‌ها را به لایه JNI ارسال می‌کند.
  • رابط برنامه‌نویسی جاوا و سی را به هم متصل می‌کند.
  • تعاملات AIDL با ISdvGateway را تعریف می‌کند:
    • ارتباطات اولیه
    • سرور RPC را پیدا یا ایجاد کنید
    • ایجاد میخانه/زیرمیزی
    • انجام تعاملات تونل داده برای کشف سرویس
    • دریافت اعلان‌ها (برای مثال، داده‌های موجود)
    • خواندن و نوشتن پیام‌ها (FMQ برای pub/sub) برای ایجاد جفت کلید و گواهی‌ها برای TLS

کتابخانه‌های کلاینت Gateway را وارد کنید

کتابخانه جاوا و JNI wrapper برای libsdvgatewayclient C API در یک APEX روی IVI target نصب شده‌اند. یک وابستگی زمان کامپایل به stub کتابخانه جاوا، کتابخانه جاوایی که باید در زمان اجرا استفاده شود و APEX مورد نیاز حاوی کتابخانه جاوا اضافه کنید.

android_app {
    name: "YourAppName",
    // ...
    static_libs: [
        "libsdvgatewayclient-java",
    ],
    libs: [
        "libsdvgatewayclient-java-sdk.stubs",
    ],
    uses_libs: [
        "libsdvgatewayclient-java-sdk",
    ],
    required: [
        "com.sdv.google.gateway.client",
    ],
    // ...
}

در مورد یک برنامه‌ی unbundled که خارج از درخت SDV ساخته شده است، روند کار مشابه است:

  1. فایل JAR کتابخانه جاوای تولید شده و JAR پشتیبان آن برای RPC را در پوشه app libs کپی کنید. برای جزئیات بیشتر، به system/software_defined_vehicle/sdv_gateway/libsdvgatewayclient_apex/README.md مراجعه کنید.

  2. فایل‌های JAR مربوط به stubها را به عنوان یک وابستگی فقط کامپایل اضافه کنید. برای مثال، با اضافه کردن یک ورودی compileOnly به بخش dependencies ، پیکربندی‌های Gradle را برای وابستگی به stubها به‌روزرسانی کنید:

    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"))
    }
    
  3. کتابخانه جاوا را به بخش 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 جاوا شامل متدهایی برای موارد زیر است:

  • وقتی واحدهای خدماتی با نوع واحد خاص (یا با نام خاص مطابقت دارند) ثبت یا لغو ثبت می‌شوند، مطلع شوید.

  • واحدهای سرویس فعلی را با نوع واحد خاص فهرست کنید (یا به طور جایگزین با نام نوع واحد سرویس خاص مطابقت دهید). برای اطلاع از تغییرات واحد سرویس، یک شنونده ایجاد کنید:

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
        }
    }
};

متد جاوا 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);
}

روش دیگر، استفاده از متد جاوا 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 ، پس از اضافه شدن، شنونده از تمام واحدهای سرویس ثبت شده مطلع می‌شود. برای دریافت فقط واحدهای سرویس ثبت شده بر اساس نام یا نوع، از listServiceUnitsByName و listServiceUnitsByType APIهای جاوا استفاده کنید:

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 از فراخوانی رویه از راه دور گوگل (gRPC) برای ارتباط با سرویس‌های SDV استفاده می‌کنند. این تعاملات به تعاریف proto از کاتالوگ VSIDL متکی هستند که با تعاریف مورد استفاده در SDV Core سازگار یا مشابه هستند. برای برنامه‌های جاوا، gRPC-Java پیاده‌سازی انتخاب شده است. یک تعریف proto سرور نمونه، 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) {}
}

پیوند به کتابخانه پروتو مربوطه:

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، بسته به پیکربندی کشف سرویس. برنامه، stubها را با شیء 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();

شبکه پیش‌فرض فرآیند را تغییر دهید

ممکن است لازم باشد از SDV-RPC VLAN به عنوان شبکه پیش‌فرض فرآیند تغییر دهید تا سوکت‌ها را برای اتصالات متصل به اینترنت باز کنید. unbindProcessFromSdvRpcNetworkInterface و bindProcessToSdvRpcNetworkInterface را برای جدا کردن و اتصال مجدد فرآیند به SDV-RPC VLAN فراخوانی کنید. این دو فراخوانی به عنوان سوئیچ‌های سراسری عمل می‌کنند و رابط شبکه‌ای را که سوکت‌ها برای همه نخ‌های فرآیند به آن متصل هستند، تغییر می‌دهند.

// 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های createPublication و publish جاوا به APIهای بومی ASDVGateway_Client_createPublication و ASDVGateway_Client_readAvailableMessages متکی هستند. برای اطلاعات دقیق در مورد API زبان C، به بخش «استفاده از SDV Gateway در یک سرویس بومی IVI» مراجعه کنید. شیء Publisher زمینه‌ای برای نوشتن پیام‌ها و مدیریت چرخه حیات انتشار فراهم می‌کند.

ایجاد مشترک با شنونده اعلان

رابط برنامه‌نویسی کاربردی جاوا (Java API) اجازه می‌دهد تا یک شنونده (listener) به عنوان پارامتر به متد subscribe to publication ارسال شود و یک شیء Subscription را برمی‌گرداند.

  • وقتی داده‌ها برای انتشار مشترک در دسترس قرار گرفت، Listener اطلاع داده می‌شود.

  • Subscription به عنوان یک شیء context عمل می‌کند و می‌تواند برای خواندن پیام‌ها و بستن اشتراک استفاده شود.

// 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();

نمونه کد

فراخوانی‌های شنونده (listener callbacks) در یک نخ واحد که توسط کلاینت مدیریت می‌شود، فراخوانی می‌شوند تا پردازش در شنونده‌ها به حداقل برسد و از تأخیر در دریافت اعلان‌ها برای سایر اشتراک‌ها جلوگیری شود. لایه جاوا از یک API زبان C برای مدیریت اشتراک، مدیریت اعلان‌ها و بازیابی پیام استفاده می‌کند. برای نمایش نحوه استفاده از API، به نمونه برنامه جاوا که در فایل باینری system/software_defined_vehicle/samples/sdv_gateway/README.md ارائه شده است، مراجعه کنید.

مجوزهای مورد نیاز

فراخوانی API کلاینت SDV Gateway به مجوزهای خاصی نیاز ندارد، اما باید قوانین SELinux مناسبی را برای برنامه‌های خود اعمال کنید.

  • برای اشتراک‌ها و انتشارات تونل داده، به هیچ مجوزی نیاز ندارید.
  • برای SDV RPC، به مجوزهای زیر نیاز دارید:
    • android.permission.INTERNET
    • android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS

برای android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS ، شما همچنین به یک فایل allowlist در زیر 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

برای استفاده از APIهای SDV Gateway، برنامه‌های جاوا به مجوزهای مشابه سرویس‌های بومی نیاز دارند. این مجوزها را با استفاده از ماکروی SELinux sdv_gateway_client_domain() اعطا کنید:

sdv_gateway_client_domain(my_oem_sdv_gateway_client_app)

تولیدکننده اصلی (OEM) دامنه my_oem_sdv_gateway_client_app را برای برنامه‌های جاوا که مجاز به استفاده از SDV Gateway هستند، تعریف می‌کند. SDV Gateway فقط از برنامه‌های سیستمی و دارای امتیاز بالا قابل استفاده است.

مکان‌های کد

کد منبع SDV Gateway را از system/software_defined_vehicle/sdv_gateway/ دریافت کنید. می‌توانید نمونه‌های SDV Gateway را برای موارد زیر دریافت کنید:

  • رابط برنامه‌نویسی کاربردی C کلاینت: system/software_defined_vehicle/samples/sdv_gateway/NativeSdvGatewayTestApp/
  • رابط برنامه‌نویسی جاوای کلاینت: system/software_defined_vehicle/samples/sdv_gateway/SdvCarMonitorTestApp/