文章

ETAS-CM

ETAS CM介绍

什么是通信管理(Communication Management)?

通信管理是自适应平台(Adaptive Platform)架构中的一个功能集群(Functional Cluster)

作为一个功能集群,通信管理向应用程序提供了一个C++ API,用于实现面向服务的通信(Service-Oriented Communication)。服务是由应用程序提供的功能单元,另一个应用程序可以在运行时动态发现并调用该服务。

通信管理的 C++ API 实现在 ara::com 命名空间中,提供面向服务的通信(包括提供者 Skeleton 和 客户端 Proxy 类),并支持服务发现(Service Discovery),使客户端和服务能够以编程方式建立连接。

该功能集群的代码作为RTA-VRTE Starter Kit 的一部分提供,以库(Library)的形式分发。因此,任何使用面向服务通信的应用程序都必须链接该库。

RTA-VRTE Starter Kit 还包括一些守护进程,例如:

  • someip_domain_gateway:用于桥接 PIPC 和 SOME/IP 通信
  • arapipcd:用于管理进程间共享内存(Shared Memory)和 IPC 服务发现(IPC Service Discovery),以及支持带 IPC 部署的 ARA 服务的 IPC 服务发现。

这些守护进程必须与应用程序一起部署到目标 ECU,并由执行管理(Execution Management)启动。

架构(Architecture)

通信管理(Communication Management)功能集群的逻辑架构包含了应用程序语言绑定(Application Language Binding)和网络绑定(Network Binding)两个部分。

其中,网络绑定(Network Binding)用于连接底层通信机制。从逻辑上来说,自适应平台(Adaptive Platform)中的通信绑定包含了多个网络绑定。

这些不同的网络绑定支持服务(Service)和客户端(Client)不在同一台机器上的系统。

RTA-VRTE Starter Kit 支持以下网络绑定:

  • SOME/IP 网络绑定
  • 进程间通信(IPC) 网络绑定

image1

事件(Events)的主要传输方式以及对应的绑定是通过 PIPC 中间件(PIPC Middleware)实现的。

此外,SOME/IP 绑定 通过 序列化的 PIPC 消息(Serialized PIPC Messages)网关应用程序(Gateway Application)提供支持。

应用程序语言绑定指的是提供给应用程序的 API。目前,通信管理(Communication Management)支持 C++11 作为应用编程接口(API)。

通信管理 API 在不同的网络绑定下保持不变,即无论使用何种网络传输方式,应用层 API 的使用方式不会受到影响。

通信管理(Communication Management)的架构类似于 Classic AUTOSAR 中的通信协议栈,具体可以进行以下类比:

  • 服务发现(Discovery) → 网络建立(Network Establishment)和仲裁(Arbitration) → Com
  • 消息分发(Dispatch) → 使用 PduR 进行网络选择
  • SOME/IP、IPC 等 → 支持不同传输机制的网络绑定

通信管理功能集群由两个静态库两个守护进程组成:

静态库

需要链接到任何使用面向服务通信(Service-Oriented Communication)的应用程序:

  • /opt/vrte/lib/libcom-communication-manager.a
  • /opt/vrte/lib/libpipc.a

守护进程

必须在相同的操作系统实例(OS-instance)上运行,以支持库的功能:

  • /opt/vrte/rb-com/bin/arapipcd
  • /opt/vrte/rb-com/bin/someip_domain_gateway

[!NOTE]

someip_domain_gateway 仅当需要使用 SOME/IP 绑定进行通信时才需要运行

交互(Interaction)

下图展示了通信管理(Communication Management)架构的基本组成部分:

image2

  • AUTOSAR 代理(Proxy)和骨架(Skeleton)应用程序 通过 ara::com 接口相互通信,并与守护进程交互。ara::com 接口的底层使用进程间通信(IPC)机制实现。
  • 服务发现(Service Discovery) 由不同的守护进程管理:
    • arapipcd:用于 IPC 绑定的服务发现管理。
    • someip_domain_gateway:用于 SOME/IP 绑定的服务发现管理。
  • 服务发现(FindService)完成后,每个 AUTOSAR 应用程序可以直接与另一个应用程序通信。

对于 SOME/IP 绑定,本地和远程机器上的网关(Gateway)会创建一个通信桥,以确保应用程序之间的正常通信: 1. 本地网关 将数据转换为 SOME/IP 数据包。 2. 该数据包通过 网络协议栈(Network Stack) 发送到 远程机器上的网关。 3. 远程网关 负责在接收端正确分发数据,确保应用程序能够正确解析和使用数据。

什么是面向服务的通信(Service-Oriented Communication)?

Classic Platform(经典平台) 中,使用的是基于信号的通信(Signal-Based Communication),而 Adaptive Platform(自适应平台) 采用的是面向服务的通信(Service-Oriented Communication)

面向服务(Service-Orientation) 通过提供和消费服务(Offering and Consumption of Services)来组织系统结构。

  • 服务(Service) 是一个逻辑单元,用于执行某项有意义的任务(常被称为“业务活动”),其具有明确的输出结果,例如:
    • 获取图像数据(Get Image Data)
    • 检查车辆速度(Check Vehicle Speed)
  • 服务是自包含的(Self-contained),只能通过明确定义的服务描述(Service Description)进行访问。
    • 对于使用者而言,服务被视为可替换的“黑盒”(Black Box),其内部结构和具体实现被隐藏在服务描述之后。
    • 只要服务描述不变,服务的内部实现可以自由修改。
  • 服务可以层次化实现(Hierarchical Implementation)
    • 上层服务 可以调用其他服务 来完成自身任务。
    • 层次化结构 促进了复用性,可以通过组合现有服务 创建新服务

面向服务架构(Service-Oriented Architecture, SOA) 非常适用于高模块化系统(Highly Modular Systems)的设计和开发:

  • 模块化 提高了服务的复用性,可以在新的应用场景中重新组合(Composability)
  • 可扩展性(Scalability):可以通过实例化多个服务 来扩展系统资源。
  • 独立开发与测试:每个服务可以独立开发、测试,并在不同系统中部署时仍能保持其行为一致性。

对比 基于信号(Signal-Based)面向服务(Service-Oriented) 的通信方式:

对比项基于信号的通信(Signal-Based Communication)面向服务的通信(Service-Oriented Communication)
数据传输方式发送方触发,定期或值变化时发送仅在有消费者请求时提供数据
带宽消耗即使接收方不需要数据,也会占用网络带宽没有消费者时不会传输数据,节省带宽
连接模式连接是固定的连接可编程动态建立
服务发现无需额外的发现机制需要服务发现机制(Service Discovery)

面向服务的通信 需要 服务发现机制,以确保服务提供方(Provider)可以向需要该服务的自适应应用程序(Adaptive Applications)分发数据。 • 该功能通过 libcom-someip-sd.so 库 实现: • 维护已提供和所需服务的注册表(Service Registry)。 • 允许动态通知服务的可用性需求

下图展示了 Service Discovery 库的实现。

image3

如前所述,在 Adaptive Platform(自适应平台) 中,服务(Service) 代表一个定义明确的活动(Well-Defined Activity),并具有特定的输出(Specific Outcome)。因此,服务逻辑上为应用程序提供所需的功能

  • 服务仅由其接口(Interface)定义,而不关心其具体实现。
  • 面向服务架构(Service-Oriented Architecture, SOA) 中,接口通常使用接口定义语言(IDL,Interface Definition Language) 来描述。
  • 在 AUTOSAR 中,IDL 采用 ARXML 格式 进行定义。

由于服务只能通过其接口进行访问,因此服务能够强封装(Strong Encapsulation)其功能:

  • 只要接口未发生变化,服务的内部实现可以自由修改,不会影响到其使用者(即客户端)。

一个 Adaptive Platform 服务 通常提供以下三种核心功能:

  1. 方法(Methods)
    • 本质上是远程过程调用(RPC,Remote Procedure Call)
    • 仅在客户端 显式请求时,服务端才会提供数据。
    • 客户端请求可以包含参数,这些参数会传递给被调用的方法,并返回响应数据
  2. 事件(Events)
    • 实现发布/订阅模式(Publisher/Subscriber Pattern)
    • 一个或多个客户端 可以注册(subscribe)某个服务。
    • 当新的数据可用时,服务器会同时向所有已注册的客户端推送更新
    • 客户端无需显式请求,数据的传输由服务端主动触发
  3. 字段(Fields)
    • 字段是方法和事件的结合
    • Get/Set 方法
    • 允许订阅者(Subscriber)更新并存储服务中的数据(类似数据库中的字段)。
    • 其他订阅者可以在稍后检索该数据。 - 事件通知
    • 当字段值更新后,服务端可以主动通知订阅者

服务发现协议(Service Discovery Protocol) 用于定位服务实例,并允许客户端在运行时选择适合的服务:

  • 定位特定的服务器实例(Specific Server Instance)
  • 发现所有特定类型的服务器(All Servers of a Particular Type)
    • 在这种情况下,客户端可以在运行时动态选择要使用的服务。

服务版本管理(Service Versioning)

服务版本管理 允许在不影响现有消费者(客户端)的情况下,对服务接口(Service Interface) 进行更改和增强。

Adaptive Platform 中,majorVersion(主版本号)minorVersion(次版本号) 这两个参数用于管理服务的版本兼容性。

  • 主版本号(majorVersion):用于表示不兼容的服务更改(backwards incompatible changes)。

  • 次版本号(minorVersion):用于表示向后兼容的服务更改(backwards compatible changes)。

这些参数可以通过 Instance Manifest Editor(在 ServiceInterfaceDeployment 选项卡中)进行配置。 在 SOME/IP 配置文件(JSON 格式)中,也会相应地反映 major 和 minor 版本值,并根据 Instance Manifest Editor 的配置进行更改。

示例

假设某个已提供的服务(Provided Service,Publisher) 被配置如下:

  • majorVersion = 1
  • minorVersion = 0

客户端(Required Service,Subscriber) 订阅该服务时,也使用相同的服务契约(Service Contract)

  • majorVersion = 1
  • minorVersion = 0

场景 1:服务向后兼容的更新

如果 服务提供方(Publisher)进行了更新,但该更新兼容旧客户端,则仅增加 minorVersion

  • 更新后的提供方服务
    • majorVersion = 1
    • minorVersion = 1
  • 客户端仍然使用旧版本
    • majorVersion = 1
    • minorVersion = 0

在这种情况下,客户端仍然可以正常使用该服务

场景 2:服务更新后不兼容旧客户端

如果 提供方服务 进行了重大更新,并且导致客户端无法与服务器通信,则必须更新 majorVersion

  • 更新后的提供方服务
    • majorVersion = 2
    • minorVersion = 1
  • 客户端仍然使用旧版本
    • majorVersion = 1
    • minorVersion = 0

在这种情况下,客户端将无法与服务提供方通信,因为主版本号(majorVersion)不同,意味着服务存在不兼容的更改。

端到端通信保护(End-to-End Communication Protection)

RTA-VRTE Starter Kit 支持 AUTOSAR E2E 协议,用于提供针对底层通信层故障的保护机制

E2E 通信保护的工作机制

  • 端到端(E2E)通信保护 通过在 SOME/IP 协议头应用层 之间添加额外的报头元素 来实现。
  • 配置完成后,E2E 保护由通信管理(Communication Management)透明处理,并可应用于:
    • 事件(Event)
    • 方法(Method)
    • 字段(Field,包含 Update、Get 和 Set 操作)
  • 在生成的代码中,唯一可用的 API 仅用于在接收方检查通信一致性

数据流在两个端点之间的传输方式可参考下图。

image4

支持的 E2E 保护配置文件(Profiles)

RTA-VRTE Starter Kit 支持以下 E2E 保护配置文件(Profiles)

  • Profile 4
  • Profile 5
  • Profile 6
  • Profile 7
  • Profile 11

不同的 E2E 配置文件 提供不同级别的通信故障检测能力,具体细节可参考 AUTOSAR E2E 协议规范(E2E Protocol Specification)

Profile 4

该配置文件使用 96-bit 头部,包含以下控制字段:

  • Length(长度):支持动态大小数据

  • Counter(计数器):检测信息重复、信息丢失、信息延迟等错误。

  • CRC(循环冗余校验):检测信息传输损坏

  • Data ID(数据 ID):检测信息插入、伪装(Masquerading)、错误寻址等错误。

该配置可在 ISOLAR-VRTE 中进行配置

Profile 5

该配置文件使用 24-bit 头部,包含以下控制字段:

  • Counter(计数器):检测信息重复、信息丢失、信息延迟等错误。
  • CRC(循环冗余校验):检测伪装、信息传输损坏等错误。
  • Data ID(数据 ID):检测伪装、错误寻址等错误。

与 Profile 4 的区别

  • Profile 5 适用于固定长度数据,而 Profile 4 支持动态大小数据
Profile 6

该配置文件的控制字段随受保护数据一起在运行时传输

  • Length(长度):支持动态大小数据,16-bit。
  • Counter(计数器):检测信息重复、信息丢失、信息延迟,8-bit。
  • CRC(循环冗余校验):检测伪装、信息传输损坏等错误。
  • Data ID(数据 ID):检测伪装、错误寻址等错误。
Profile 7

该配置文件使用 160-bit 头部,包含以下控制字段:

  • Length(长度):支持动态大小数据
  • Counter(计数器):检测信息重复、信息丢失、信息延迟等错误。
  • CRC(循环冗余校验):检测信息传输损坏
  • Data ID(数据 ID):检测信息插入、伪装、错误寻址等错误。

与 Profile 4 的区别

  • Profile 7 支持更大的数据长度
  • 采用不同的 CRC 多项式
Profile 11

与 Profile 1 兼容的总线协议,但在接收端提供与 Profile 4~7 类似的增强功能

  • Counter(计数器):检测信息重复、信息丢失、信息延迟,4-bit。
  • CRC(循环冗余校验):检测信息传输损坏,8-bit。
  • Data ID(数据 ID):检测信息插入、伪装、错误寻址等错误。

Profile 11 省略了一些已废弃的 Data ID 模式(DataIDModes)

E2E 配置(Configuration)

ISOLAR-VRTE Instance Manifest Editor(实例清单编辑器)中,提供了一个用户界面,用于配置服务实例(Service Instances)及其 E2E 保护配置文件(E2E Profile)属性

下表描述了每个 E2E 保护配置文件(Profile) 中的控制字段(Control Fields)ISOLAR-VRTE 允许配置的参数之间的关系:

E2E保护字段Profile 4Profile 5Profile 6Profile 7Profile 11
Data ID支持支持支持支持支持
Data Length不支持支持不支持不支持支持
Min Data Length支持不支持支持支持不支持
Max Data Length支持不支持支持支持不支持

Profile 4 和 Profile 7 支持 可变数据长度,因此必须配置:

  • Min Data Length(最小数据长度)
  • Max Data Length(最大数据长度)

Profile 5 仅支持固定数据长度,因此只需填写 Data Length(数据长度)

Profile 6 也支持动态数据长度,因此需要 Min Data Length 和 Max Data Length

验证 E2E 通信(Verifying E2E Communication)

E2E 保护的通信状态(即数据是否可信)取决于当前通信状态,以及配置的 E2E 保护机制是否被违反。

不同的状态值由 E2E 状态机(E2E State Machine) 表示,具体流程可参考下图。

image5

通信完整性(即数据是否可靠)可以使用自动生成的 API 进行检查。 GetE2EStateMachineState() API 返回一个 SMState 枚举实例,用于查询 E2E 保护的事件(Event) 的当前状态。

1
2
3
4
5
if (m_proxy->araCM_Event.GetE2EStateMachineState() == 
    ara::com::e2e::SMState::kValid) 
{
    // 数据可以安全使用
}
  • 如果返回 kValid,表示数据是有效的,可以安全地在应用程序中使用。
  • 如果返回 kNoData 或 kInit,表示数据缺失或初始化未完成
  • 如果返回 kInvalid,表示通信完整性遭到破坏,可能由于:
    • 错误次数过多(Too many errors)
    • 成功校验次数太少(Too few OK checks)
    • 此时,接收的数据不可信,不能使用
  • 如果返回 kStateMDisabled,表示当前事件未配置 E2E 状态机

除了检查整个通信状态,还可以使用 GetProfileCheckStatus() 方法检查单个数据样本的状态,可能的状态包括:

  • kOk → 数据有效
  • kRepeated → 数据计数器重复
  • kNoData → 数据缺失
  • kInvalid → 数据不可信

CM测试脚本的使用以及测试程序部署

测试前准备

程序部署

CM需要两台主机才能进行测试,我所使用的IP是192.168.25.128,192.168.25.129。需要提供两台测试机器。

首先需要两台目标主机上分别执行以下命令,建立软链接:

1
sudo ln -s /lib/x86_64-linux-gnu/ld-2.31.so /lib/ld-linux-x86-64.so.2

将192.168.25.128,192.168.25.129目录下的vrte.tar.gz分别拷贝至目标主机的/opt目录下,然后执行以下命令,解压压缩包:

1
tar -xvf vrte.tar.gz

[!WARNING]

192.168.25.128目录存放的是skeleton相关的程序,192.168.25.129目录存放的是proxy相关的程序。必须记住哪台主机存放了skeleton,哪台主机存放了proxy,之后修改robot脚本的时候需要。

随后分别在两台目标主机执行以下命令,用以添加exmd.sh的执行权限:

1
chmod +x /opt/vrte/usr/bin/exmd.sh

在两台目标主机上,分别修改

1
/opt/vrte/rb-com/etc/MachineDesign_A_someip.json

1
/opt/vrte/rb-com/etc/MachineDesign_B_someip.json

将json中的192.168.25.128或192.168.25.129修改成实际主机的IP地址。例如:

image6

[!WARNING]

需要全部修改完毕。

脚本修改

修改工程目录下的gpulse-test-framework/tests/cm-test.robot文件。

1
2
3
4
*** Variables ***
${HOSTNAME_1}    192.168.25.128
${HOSTNAME_2}    192.168.25.129
${PRIVATE_KEY}    C:/Users/31762/.ssh/id_ed25519
  • HOSTNAME_1的IP地址修改为在程序部署中存放skeleton程序的主机IP。
  • HOSTNAME_2的IP地址修改为在程序部署中存放proxy程序的主机IP。
  • 将PRIVATE_KEY的路径修改为存放SSH密钥的路径。

执行测试套件

激活第一章部署的RF测试环境,启动ride进行测试

1
2
conda activate <rf_env>
ride

选择菜单栏File,打开测试工程:

image7

如下图所示,可选中所需的测试项进行测试,或者选择所有该测试脚本的测试案例进行测试。

image8

选中所需的测试案例,点击菜单栏下方的”run”按钮,或者切换右侧的run栏->start。同时,可以通过”Log options”指定测试的log信息以及report输出路径。

image9

测试结果查看

报告文件路径为测试前通过Log options指定: 若未指定结果输出路径,可通过Report、Log选项查看

image10

CM相关Keyworld说明

  1. Valid Service Discovery
keyworldkeyworld说明参数参数说明
Valid Service Discovery判断proxy是否发现了服务;若日志中没有已发现服务的信息,则抛出异常content日志内容
  deployment_id服务id
  instance_id实例id
  major_version主版本号
  minor_version次版本号
  1. Valid Service Is Stopped
keyworldkeyworld说明参数参数说明
Valid Service Is Stopped判断服务是否已经关闭;若日志中没有服务已关闭的信息,则抛出异常content日志内容
  deployment_id服务id
  instance_id实例id
  major_version主版本号
  minor_version次版本号
  1. Valid Event
keyworldkeyworld说明参数参数说明
Valid Event判断是否收到event消息;若日志中没有相关的event信息,则抛出异常content日志内容
  deployment_id服务id
  instance_id实例id
  event_id事件id
  1. Valid E2E
keyworldkeyworld说明参数参数说明
Valid E2E判断收到的event消息是否e2e检验成功;若日志中有e2e检验失败的日志信息,则抛出异常content日志内容
  deployment_id服务id
  instance_id实例id
  event_id事件id
  1. Valid Method
keyworldkeyworld说明参数参数说明
Valid Method判断是否收到method消息;若日志中没有相关的method信息,则抛出异常content日志内容
  deployment_id服务id
  instance_id实例id
  method_id方法id

[!NOTE]

Method一般情况下,是双向的。proxy需要调用method将参数发送给skeleton,而skeleton需要将method结果返回给proxy。所以Valid Method一般需要在客户端和服务端都做判断。

  1. Field

[!NOTE]

field相关的keywrold请参考event和method。

测试用例举例说明

测试用例如下表所示:

用例ID测试目的操作步骤期望结果备注
SW_Req_CM_TC_P2_01在订阅event后。Proxy侧能否收到来自skeleton侧发送的event1.在两台机器上分别运行运行cm-demo-proxy和cm-demo-skeleton
2.在运行cm-demo-proxy的机器终端上输入echo normal > /tmp/event
在第2步执行后,proxy侧的日志显示收到了event输入normal,表示正常的event通信

对应的测试脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sw Req CM TC P2 01
    ${ssh1}=    Open SSH Connection With Key    ${HOSTNAME_1}    ${PRIVATE_KEY}
    Start Test Program And Enable Log Capture    ${ssh1}    ${LOG_FILE_NAME_1}
    ${ssh2}=    Open SSH Connection With Key    ${HOSTNAME_2}    ${PRIVATE_KEY}
    Start Test Program And Enable Log Capture    ${ssh2}    ${LOG_FILE_NAME_2}
    Wait For Program Startup    10s

    Run Remote Command    ${ssh2}    echo normal > /tmp/event
    Wait    10s
    ${log}=    Retrieve Test Program Log    ${ssh2}    ${LOG_FILE_NAME_2}
    Valid Event     ${log}    11    17    56

    Reboot the remote PC    ${ssh1}
    Reboot the remote PC    ${ssh2}
语句说明
${ssh1}= Open SSH Connection With Key ${HOSTNAME_1} ${PRIVATE_KEY}使用密钥和目标主机IP地址建立SSH连接(和运行skeleton主机建立SSH连接)
Start Test Program And Enable Log Capture ${ssh1} ${LOG_FILE_NAME_1}开启测试程序,并保存程序日志(启用skeleton侧测试程序)
${ssh2}= Open SSH Connection With Key ${HOSTNAME_2} ${PRIVATE_KEY}使用密钥和目标主机IP地址建立SSH连接(和运行proxy主机建立SSH连接)
Start Test Program And Enable Log Capture ${ssh2} ${LOG_FILE_NAME_2}开启测试程序,并保存终端日志(启用proxy侧测试程序)
Wait For Program Startup 10s为了EM将测试程序拉起来,等待10s
Run Remote Command ${ssh2} echo normal > /tmp/event使用SSH在运行proxy主机上执行echo normal > /tmp/event
Wait 10s等待10s
${log}= Retrieve Test Program Log ${ssh2} ${LOG_FILE_NAME_2}从运行proxy主机上获取程序日志
Valid Event ${log} 11 17 56判断日志中是否有service id: 11,instance id: 17,event id: 56的event消息日志
Reboot the remote PC重启远程主机

测试程序的开发

由于ETASCM本身没有任何相关的日志,若需要测试其他CM自适应程序。需要简单修改CM自适应程序的源码。

1. 导入cm_helper.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <cstdint>
#include <cstdio>

namespace cm_helper {

using ServiceId = std::uint64_t;
using InstanceId = std::uint64_t;
using MajorVersion = std::uint64_t;
using MinorVersion = std::uint64_t;
using EventId = std::uint64_t;
using MethodId = std::uint64_t;

inline void print_service_discovery(ServiceId service_id,
                                    InstanceId instance_id,
                                    MajorVersion major_version = 0,
                                    MinorVersion minor_version = 0) {
  std::printf(
      "cm_helper : invoking availability handler { service: 0x%04lx "
      "instance: 0x%04lx major: 0x%02lx minor: 0x%08lx available: true\n",
      service_id, instance_id, major_version, minor_version);
}

inline void print_service_is_stopped(ServiceId service_id,
                                     InstanceId instance_id,
                                     MajorVersion major_version = 0,
                                     MinorVersion minor_version = 0) {
  std::printf(
      "cm_helper : invoking availability handler { service: 0x%04lx "
      "instance: 0x%04lx major: 0x%02lx minor: 0x%08lx available: false\n",
      service_id, instance_id, major_version, minor_version);
}

inline void print_event(ServiceId service_id, InstanceId instance_id,
                        EventId event_id) {
  event_id += 0x8000;
  std::printf("cm_helper : invoking message handler { [ event ] service: "
              "0x%04lx instance: 0x%04lx event: 0x%04lx\n",
              service_id, instance_id, event_id);
}

inline void print_event_e2e(ServiceId service_id, InstanceId instance_id,
                            EventId event_id, bool is_ok) {
  event_id += 0x8000;
  if (is_ok)
    std::printf("cm_helper : invoking message handler { [ event ] service: "
                "0x%04lx instance: 0x%04lx event: 0x%04lx check: true\n",
                service_id, instance_id, event_id);
}

inline void print_method(ServiceId service_id, InstanceId instance_id,
                         MethodId method_id) {
  std::printf("cm_helper : invoking message handler { [ method ] service: "
              "0x%04lx instance: 0x%04lx method: 0x%04lx\n",
              service_id, instance_id, method_id);
}

inline void print_field_notify(ServiceId service_id, InstanceId instance_id,
                               EventId event_id) {
  print_event(service_id, instance_id, event_id);
}

inline void print_field_get(ServiceId service_id, InstanceId instance_id,
                            MethodId method_id) {
  print_method(service_id, instance_id, method_id);
}

inline void print_field_set(ServiceId service_id, InstanceId instance_id,
                            MethodId method_id) {
  print_method(service_id, instance_id, method_id);
}

} // namespace cm_helper

自适应程序工程目录中,创建一个cm_helper.h的文件,复制上面的代码。随后在需要进行CM测试的程序源码中引入此头文件

1
#include "cm_helper.h"

2. 服务发现的测试

根据cm_helper.h,提供了两个服务发现相关的日志打印函数,一个输出服务已发现的日志(print_service_discovery),一个输出服务已停止的日志(print_service_is_stopped)。在proxy侧服务发现的回调函数中,插入以下代码:

1
2
3
4
5
6
7
8
9
10
11
auto l_handler = [&](ara::com::ServiceHandleContainer<it_usrdoc::Service_InterfaceProxy::HandleType> f_container,
                       ara::com::FindServiceHandle f_handle) {
  // 插入的代码
     if (!f_container.empty()) {
        cm_helper::print_service_discovery(11, 23, 1, 0);
     } else {
        cm_helper::print_service_is_stopped(11, 23, 1, 0);
     }
  // ......
}
it_usrdoc::Service_InterfaceProxy::StartFindService(l_handler, l_instance);

[!NOTE]

CM的服务发现的回调函数是通过f_container是否为来判断是否发现服务以及服务是否停止。

3. Event的测试

根据cm_helper.h,提供了event数据是否成功接受以及判断判断e2e是否成功的日志打印函数。在event的回调函数中

1
2
3
4
5
6
7
8
auto result = f_proxy_p.GetNewSamples( [&]( const ara::com::SamplePtr<const T> f_sample_p ) {
// 插入的代码
     cm_helper::print_event(11, 17, 56);
// 若配置了E2E,插入以下代码
     cm_helper::print_event_e2e(11, 24, 56, f_sample_p.GetProfileCheckStatus() == ara::com::e2e::ProfileCheckStatus::kOk);
// ......

}

4. Method的测试

根据cm_helper.h,提供了打印method信息的函数,可以用以判断skeleton是否接收到method参数以及proxy是否收到method返回值。

skeleton侧

skeleton侧需要在method的实现函数体插入print_method函数,如下:

1
2
3
4
5
6
ara::core::Future<etas::com::OutputMethodIO> SampleSkeleton::MethodIO(const MsgRequest input)
{
    // 插入的代码
    cm_helper::print_method(11, 18, 12);
    // ......
}

proxy侧

proxy侧需要收到method的返回值时插入print_method函数,如下:

1
2
3
4
5
6
7
// 掉用method
l_future     = l_proxy->MethodIO(l_input);
// ......
// 获取method的结果,也可以使用l_future.get
ara::core::Result<etas::com::OutputMethodIO, ara::core::ErrorCode> result = l_future.GetResult();
// 打印method
cm_helper::print_method(11, 18, 12);

5. Field的测试

fieldeventmethod的结合体,可以参考上面两种。/

本文由作者按照 CC BY 4.0 进行授权