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) 网络绑定
事件(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)架构的基本组成部分:
- 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 库的实现。
如前所述,在 Adaptive Platform(自适应平台) 中,服务(Service) 代表一个定义明确的活动(Well-Defined Activity),并具有特定的输出(Specific Outcome)。因此,服务逻辑上为应用程序提供所需的功能。
- 服务仅由其接口(Interface)定义,而不关心其具体实现。
- 在面向服务架构(Service-Oriented Architecture, SOA) 中,接口通常使用接口定义语言(IDL,Interface Definition Language) 来描述。
- 在 AUTOSAR 中,IDL 采用 ARXML 格式 进行定义。
由于服务只能通过其接口进行访问,因此服务能够强封装(Strong Encapsulation)其功能:
- 只要接口未发生变化,服务的内部实现可以自由修改,不会影响到其使用者(即客户端)。
一个 Adaptive Platform 服务 通常提供以下三种核心功能:
- 方法(Methods)
- 本质上是远程过程调用(RPC,Remote Procedure Call)。
- 仅在客户端 显式请求时,服务端才会提供数据。
- 客户端请求可以包含参数,这些参数会传递给被调用的方法,并返回响应数据。
- 事件(Events)
- 实现发布/订阅模式(Publisher/Subscriber Pattern)。
- 一个或多个客户端 可以注册(subscribe)某个服务。
- 当新的数据可用时,服务器会同时向所有已注册的客户端推送更新。
- 客户端无需显式请求,数据的传输由服务端主动触发。
- 字段(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 仅用于在接收方检查通信一致性。
数据流在两个端点之间的传输方式可参考下图。
支持的 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 4 | Profile 5 | Profile 6 | Profile 7 | Profile 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) 表示,具体流程可参考下图。
通信完整性(即数据是否可靠)可以使用自动生成的 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地址。例如:
[!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,打开测试工程:
如下图所示,可选中所需的测试项进行测试,或者选择所有该测试脚本的测试案例进行测试。
选中所需的测试案例,点击菜单栏下方的”run”按钮,或者切换右侧的run栏->start。同时,可以通过”Log options”指定测试的log信息以及report输出路径。
测试结果查看
报告文件路径为测试前通过Log options指定: 若未指定结果输出路径,可通过Report、Log选项查看
CM相关Keyworld说明
- Valid Service Discovery
| keyworld | keyworld说明 | 参数 | 参数说明 |
|---|---|---|---|
| Valid Service Discovery | 判断proxy是否发现了服务;若日志中没有已发现服务的信息,则抛出异常 | content | 日志内容 |
| deployment_id | 服务id | ||
| instance_id | 实例id | ||
| major_version | 主版本号 | ||
| minor_version | 次版本号 |
- Valid Service Is Stopped
| keyworld | keyworld说明 | 参数 | 参数说明 |
|---|---|---|---|
| Valid Service Is Stopped | 判断服务是否已经关闭;若日志中没有服务已关闭的信息,则抛出异常 | content | 日志内容 |
| deployment_id | 服务id | ||
| instance_id | 实例id | ||
| major_version | 主版本号 | ||
| minor_version | 次版本号 |
- Valid Event
| keyworld | keyworld说明 | 参数 | 参数说明 |
|---|---|---|---|
| Valid Event | 判断是否收到event消息;若日志中没有相关的event信息,则抛出异常 | content | 日志内容 |
| deployment_id | 服务id | ||
| instance_id | 实例id | ||
| event_id | 事件id |
- Valid E2E
| keyworld | keyworld说明 | 参数 | 参数说明 |
|---|---|---|---|
| Valid E2E | 判断收到的event消息是否e2e检验成功;若日志中有e2e检验失败的日志信息,则抛出异常 | content | 日志内容 |
| deployment_id | 服务id | ||
| instance_id | 实例id | ||
| event_id | 事件id |
- Valid Method
| keyworld | keyworld说明 | 参数 | 参数说明 |
|---|---|---|---|
| Valid Method | 判断是否收到method消息;若日志中没有相关的method信息,则抛出异常 | content | 日志内容 |
| deployment_id | 服务id | ||
| instance_id | 实例id | ||
| method_id | 方法id |
[!NOTE]
Method一般情况下,是双向的。proxy需要调用method将参数发送给skeleton,而skeleton需要将method结果返回给proxy。所以
Valid Method一般需要在客户端和服务端都做判断。
- Field
[!NOTE]
field相关的keywrold请参考event和method。
测试用例举例说明
测试用例如下表所示:
| 用例ID | 测试目的 | 操作步骤 | 期望结果 | 备注 |
|---|---|---|---|---|
| SW_Req_CM_TC_P2_01 | 在订阅event后。Proxy侧能否收到来自skeleton侧发送的event | 1.在两台机器上分别运行运行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 | 重启远程主机 |
测试程序的开发
由于ETAS的CM本身没有任何相关的日志,若需要测试其他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的测试
field是event和method的结合体,可以参考上面两种。/









