CommonAPI学习笔记-1
CommonAPI学习笔记-1
一. 整体结构
CommonAPI分为两层:核心层和绑定层,使用了Franca来描述服务接口的定义和部署,而Franca是一个用于定义和转换接口的框架(https://franca.github.io/franca/)。
核心层和通信中间件无关,使用fidl配置文件来描述接口的逻辑,绑定层和中间件相关(DBUS/SOMEIP)。因此在fdepl配置文件中设置了中间件所依赖的参数。(CommonAPI C++ is divided up in a middleware-independent part (CommonAPI Core) and in a middleware-specific part(CommonAPI Binding) )
通过代码生成工具(core-generator, someip-generator和dbus-generator)根据配置文件生成核心层和绑定层的业务代码,这些业务代码依赖CommonAPI的运行库libCommonAPI.so,而CommonAPI的运行库则依赖CommonAPI的绑定库(libCommonAPI-SomeIP.so),CommonAPI的绑定库依赖具体的中间件运行时库 (libsomep.so)。
二. Franca 配置文件
前面说过CommonAPI分为核心层和绑定层,核心层和绑定层都有代码生成工具,我们需要根据业务场景的实际需求,编写对应的fidl(核心层)和fdepl(绑定层)配置文件,并且由代码生成工具生成代码。
fidl文件主要由下列元素组成:
类型 | 说明 |
---|---|
package | 接口所属的名称空间,类似JAVA的包名或者DDS中的domainid |
interface | 接口名称,相当于someip中的serviceid |
version | 接口版本号,这个版本号同样会被绑定层使用到 |
attribute | 接口中的属性成员,对外提供读和写的接口 |
typeCollection | 用于自定义类型的容器,内部保存用户自己定义的数据类型 |
struct | 用户自定义数据类型,类似C语言的结构体 |
fidl文件的范例(来自官方代码https://github.com/COVESA/capicxx-core-tools/blob/master/CommonAPI-Examples/E02Attributes/fidl/E02Attributes.fidl):
/* Copyright (C) 2015 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package commonapi.examples // package包名
interface E02Attributes { // interface接口名称
version { major 1 minor 0 } // 接口版本号
attribute Int32 x // 基本数据类型属性
attribute CommonTypes.a1Struct a1 // 用户自定义数据类型属性
}
typeCollection CommonTypes { // 用户自定义类型集合
version { major 1 minor 0 } // 类型版本号
struct a1Struct { // 用户自定义类型
String s
a2Struct a2 // 嵌套了用户自定义类型成员
}
struct a2Struct { // 用户自定义类型
Int32 a
Boolean b
Double d
}
}
fdepl文件对应的是绑定层的代码实现,因此,fdepl文件中的配置是和绑定层对应的中间件相对应的,例如如果fidl中定义了接口中的属性,那么如果绑定层是someip的话,那么fdepl文件中需要指定接口对应的someip服务的信息(serviceid, instanceid),属性对应的方法信息(method, eventid, eventgroupid)等,这里用官方范例中的fdepl文件(https://github.com/COVESA/capicxx-core-tools/blob/master/CommonAPI-Examples/E02Attributes/fidl/E02Attributes-SomeIP.fdepl)来说明
/* Copyright (C) 2015 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-4-SOMEIP_deployment_spec.fdepl"
import "E02Attributes.fidl" // 重点,这个import就意味着如果不先生成fidl对应的代码文件,可能无法生成该fdepl文件的代码文件
// 定义了commonapi.examples.E02Attributes这个服务接口的CommonAPI SOME/IP部署规范(SOMEIP服务的参数配置)
define org.genivi.commonapi.someip.deployment for interface commonapi.examples.E02Attributes { //这里会生成部署文件
/* service id = 0x1235*/
SomeIpServiceID = 4661 // fidl中服务接口对应someip服务id
attribute x { // fidl的接口中,属性对应someip的属性(包含get和set方法以及notifier对应的事件和事件组)
SomeIpGetterID = 3000 // somep属性的get方法 method_id
SomeIpSetterID = 3001 // somep属性的set方法 method_id
SomeIpNotifierID = 33010 // // somep属性的notifier对应的event_id
SomeIpNotifierEventGroups = { 33010 } // 事件组
SomeIpAttributeReliable = true // someip属性的通信方式(TCP/UDP)
}
attribute a1 {
SomeIpGetterID = 3002
SomeIpSetterID = 3003
SomeIpNotifierID = 33011
SomeIpNotifierEventGroups = { 33011 }
SomeIpAttributeReliable = true
}
}
// 只是将fidl中用户自定义类型在这里简单的申明了一下,实际测是下来是可以删除的,并不影响绑定层代码的生成和编译,删除也会生成绑定层的CommonTypes代码文件
define org.genivi.commonapi.someip.deployment for typeCollection commonapi.examples.CommonTypes {
struct a1Struct {
}
struct a2Struct {
}
}
// 这部分为CommonAPI接口实例和SOME/IP服务提供者(provider)之间的关系
define org.genivi.commonapi.someip.deployment for provider as Service { // 实例关系申明开始
instance commonapi.examples.E02Attributes { // E02Attributes接口实例1
InstanceId = "commonapi.examples.Attributes" // E02Attributes接口实例名称
SomeIpInstanceID = 22136 // someip的service instance_id // 对应的SOMEIP服务instance_id
}
// 下面其实可以申明多个实例的
} // 实例关系申明结束
三. Generate Code
-
fidl生成的代码文件:
-
fdepl生成的代码文件:
-
依赖关系:
用户应用程序是不需要关心绑定层的,用户应用程序直接和核心层配置文件生成的代码打交道,通过CommonAPI核心层的运行时Runtime创建核心层的Proxy和Stub(E02AttributesProxy,E02AttributesStub)。
用户应用程序创建E02AttributesProxy和E02AttributesStub对象,这两个对象都是核心层的对象,实现了CommonAPI核心层的Proxy和Stub接口。
可以看到fidl生成出来的Proxy和Stub类都是核心层的Proxy和Stub接口的实现类。
绑定层生成的Proxy和Stub类在程序开始运行的时候就将自己的构造函数注册到绑定层的工厂中,构造函数和CommonAPI的接口名进行绑定,如下:
INITIALIZER宏是用于展开其中的函数定义,将函数定义为跟随程序模块启动时自动加载运行,因此,INITIALIZER宏中定义的函数在程序的main函数之前就被执行了,INITIALIZER宏中会将fdepl生成的绑定层Proxy/Stub的初始化函数注册给SomeIP/DBus绑定层Factory (CommonAPI::SomeIP: : Factory),后者会在初始化的时候执行注册进来的所有绑定层Proxy/Stub的初始化函数,大致的时序如下:
可以从上面的时序图看出来,注册给CommonAPI::SomeIP::Factory的函数(initializeHelloWorldSomeIPProxy)的用途有两个:
-
注册核心层interface和绑定层中间件服务之间的对应关系
CommonAPI::SomeIP::AddressTranslator::get()->insert( "local:commonapi.examples.E02Attributes:v1_0:commonapi.examples.Attributes", 0x1235, 0x5678, 1, 0); // AddressTranslator中记录了commonapi interface和Someip Service信息之间的对应关系
-
注册绑定层Proxy/Stub的创建函数
CommonAPI::SomeIP::Factory::get()->registerProxyCreateMethod( "commonapi.examples.E02Attributes:v1_0", // CommonAPI Interface &createE02AttributesSomeIPProxy); // 绑定层根据fdepl生成Proxy的创建函数
CommonAPI::SomeIP::Factory::get()->registerStubAdapterCreateMethod( "commonapi.examples.E02Attributes:v1_0", // CommonAPI Interface &createE02AttributesSomeIPStubAdapter); // 绑定层根据fdepl生成Stub的创建函数
生成的核心层的Proxy和Stub代码的主要作用是根据fidl中定义的接口规范生成对应的接口客户端和服务端的接口代码,此外还有一个作用就是实现getInterface接口,这个接口会返回fidl中定义的服务的interface接口名称。创建核心层的Proxy和Stub时需要指定Common的Domain,interface接口名称和Instanceid,依据这三个参数核心层怎么知道该怎么创建绑定层的Proxy和Stub,有怎么知道绑定层的通信参数(例如service_id,instance_id)呢?
这个就要依赖生成的绑定层代码在INITIALIZER宏注册的initialize函数来完成了,每个绑定层工厂中有一个map容器保存CommonAPI的interface和该绑定层通信地址的对应关系:
CommonAPI::SomeIP::AddressTranslator::get()->insert(
"local:commonapi.examples.E02Attributes:v1_0:commonapi.examples.Attributes",
0x1235, 0x5678, 1, 0); // AddressTranslator中记录了commonapi interface和Someip Service信息之间的对应关系
CommonAPI::SomeIP::Factory::get()->registerProxyCreateMethod(
"commonapi.examples.E02Attributes:v1_0", // CommonAPI Interface
&createE02AttributesSomeIPProxy); // 绑定层根据fdepl生成Proxy的创建函数
核心层的createProxy/registerStub函数会将让手下目前支持的两个绑定层(DBus和SomeIP)去各自Factory中看下是否注册过这次传入的CommonAPI的interface,如果有的话就能找到对应的中间件的通信参数,创建中间件的服务实例,这部分代码逻辑在CommonAPI::Runtime::createProxyHelper中:
std::shared_ptr<Proxy>
Runtime::createProxyHelper(const std::string &_domain, const std::string &_interface, const std::string &_instance,
const std::string &_connectionId, bool _useDefault) {
std::lock_guard<std::mutex> itsLock(factoriesMutex_);
for (auto factory : factories_) { // 绑定层工厂容器,包括someip/dbus
//如果通过AddressTranslator注册过,那么这里就能创建出绑定层的Proxy
std::shared_ptr<Proxy> proxy
= factory.second->createProxy(_domain, _interface, _instance, _connectionId);
if (proxy)
return proxy;
}
return (_useDefault && defaultFactory_ ?
defaultFactory_->createProxy(_domain, _interface, _instance, _connectionId)
: nullptr);
}
由于生成的Proxy代码已经在INITIALIZER中调用了registerProxyCreateMethod注册了创建绑定层Proxy的函数,因此绑定层工厂就可以找到该函数来创建绑定层Proxy,代码如下:
std::shared_ptr<CommonAPI::Proxy> // CommonAPI-SomeIP绑定层工厂创建绑定层Proxy
Factory::createProxy(
const std::string &_domain, // CommonAPI的domain
const std::string &_interface, const std::string &_instance, // CommonAPI的interface和instance
const ConnectionId_t &_connectionId) {
auto proxyCreateFunctionsIterator
= proxyCreateFunctions_.lower_bound(_interface);
if (proxyCreateFunctionsIterator
!= proxyCreateFunctions_.end()) {
std::string itsInterface(_interface);
if (proxyCreateFunctionsIterator->first != _interface) { // 首先根据interface找注册过的绑定层Proxy创建函数
std::string itsInterfaceMajor(_interface.substr(0, _interface.find('_')));
if (proxyCreateFunctionsIterator->first.find(itsInterfaceMajor) != 0)
return nullptr; //如果该Interface没有注册过绑定层创建函数,则直接返回nullptr
itsInterface = proxyCreateFunctionsIterator->first;
}
CommonAPI::Address address(_domain, itsInterface, _instance);
Address someipAddress; // 保存绑定层中间件通信的地址参数
// 从AddressTranslator中找注册的绑定层Proxy的创建函数
if (AddressTranslator::get()->translate(address, someipAddress)) {
std::shared_ptr<Connection> itsConnection // 这个Connection类封装了对vsomeip中间件的调用
= getConnection(_connectionId);
if (itsConnection) {
std::shared_ptr<Proxy> proxy
= proxyCreateFunctionsIterator->second( //使用注册的prox创建函数生成fdepl中的绑定层Proxy
someipAddress, itsConnection);
if (proxy && proxy->init()) // 初始化绑定层Proxy
return proxy;
}
}
}
COMMONAPI_ERROR("Creating proxy for \"", _domain, ":", _interface, ":",
_instance, "\" failed!");
return nullptr;
}
四. 基本开发流程
-
安装vsomeip库
https://blog.csdn.net/qq_43655213/article/details/129344035
-
安装commonapi代码生成工具
https://blog.csdn.net/qq_43655213/article/details/129344035
-
编译并且安装commonapi代码库
https://blog.csdn.net/qq_43655213/article/details/129344035
-
编写fidl和fdepl
https://github.com/COVESA/capicxx-core-tools/blob/master/CommonAPI-Examples/E02Attributes/fidl/E02Attributes-SomeIP.fidl
https://github.com/COVESA/capicxx-core-tools/blob/master/CommonAPI-Examples/E02Attributes/fidl/E02Attributes-SomeIP.fdepl
-
生成generated-code
如下两行命令会在当前目录下创建src-gen目录并且生成核心层和绑定层的Proxy和Stub相关代码文件:
commonapi-core-generator-linux-x86_64 -sk E02Attributes-SomeIP.fidl
commonapi-someip-generator-linux-x86_64 -ll verbose E02Attributes-SomeIP.fdepl
-
编写客户端应用
客户端:
a. 指定创建的domain, instance, connection_name
std::string domain = "local"; std::string instance = "commonapi.examples.Attributes"; std::string connection = "client-sample";
b. 通过CommonAPI::Runtime创建Proxy
std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get(); auto myProxy = runtime->buildProxyWithDefaultAttributeExtension<E02AttributesProxy, CommonAPI::Extensions::AttributeCacheExtension>(domain, instance, connection); // 等待对端stub可用 std::cout << "Waiting for service to become available." << std::endl; while (!myProxy->isAvailable()) { std::this_thread::sleep_for(std::chrono::microseconds(10)); }
c. 调用Proxy中实现的fidl定义的接口
服务端:
a. 指定创建的domain, instance, connection_name
std::string domain = "local";
std::string instance = "commonapi.examples.Attributes";
std::string connection = "service-sample";
b. 创建绑定层Stub对象
std::shared_ptr<E02AttributesStubImpl> myService = std::make_shared<E02AttributesStubImpl>();
c. 注册绑定层Stub对象到SomeIP绑定层工厂
while (!runtime->registerService(domain, instance, myService, connection)) {
std::cout << "Register Service failed, trying again in 100 milliseconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
注意:E02AttributesStubImpl是需要用户自行实现fdepl生成的绑定层Stub抽象类的接口的,fdepl生成的绑定层Stub类是个抽象类,因为毕竟只有用户才知道该怎么实现这个接口,生成工具做不到的。