Genivi commonapi, some/ip
최근 자동차의 In-Vehicle Network는 기존의 signal oriented 방식(CAN, LIN등)에서 이더넷을 도입하면서 Service oriented 방식으로 전환해 가고 있다. 이 부분에서 사용되는 프로토콜이 BMW에서 개발하고 밀고 있는 SOME/IP 이다. (Scalable service Oriented Middleware over IP). SOME/IP를 GENIVI측에서 open-source로 개발해 놓은 것이 vsomeip 이다. 얘를 linux 머신에 설치해 보고 간단한 HelloWorld service와 client를 구현하는 내용이다.
우선 먼저 java8 RE를 설치 한다. (다른 java버전에서 에러나는 경우가 있어서 java8 RE를 추천함.)
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
javac -version 또는 java -version
여러개의 java 버전이 설치된 경우 아래 명령으로 선택 가능함. automode는 항상 가장 높은 버전이 우선 사용되는 것이고 manualmode는 선택한 버전이 실행되는 것임.
sudo update-alternatives --config java
또한 위 명령으로 java가 어디에 설치되어 있는지 알 수 있다. 이를 통해 JAVA_HOME환경 변수를 설정할 수 있다.
Selection Path Priority Status
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
0 /usr/lib/jvm/java-8-oracle/jre/bin/java 1081 auto mode
* 1 /usr/lib/jvm/java-8-oracle/jre/bin/java 1081 manual mode
echo $JAVA_HOME으로 확인 해보고 설정이 안되어 있으면
vi /etc/environment 파일을 열어서 다음을 입력 (패스는 다를 수 있음)
JAVA_HOME="/usr/lib/jvm/java-8-oracle"source /etc/environment
echo $JAVA_HOME
설치 과정에 관심이 없다면 아래의 링크를 참고하면된다.
https://github.com/endland/build-common-api-cpp-native 스크립트로 단방에 설치 가능, 혹시 link 에러가 나면 아래와 같이 패스를 추가 해준다.
export LD_LIBRARY_PATH=/home/kzi/Projects/genivi/build-common-api-cpp-native/install/lib:$LD_LIBRARY_PATH
========commonAPI C++ in 10 minutes(with SOME/IP)====
한방에 설치하지 않고 차근 차근 해보고 싶은 경우 다음을 참고 하자.
step1&2 : 사전 준비 사항, CommonAPI 3.1.10으로 테스트 함.
ubuntu16.04LTS, git, cmake≥2.8), Java8runtime environment
git clone https://github.com/GENIVI/capicxx-core-runtime.git
이제 CommonAPI runtime library가 다른 dependencies 필요 없이 build가능.
cd capicxx-core-runtime/
mkdir build
cd build
cmake ..
make
Doxygen이나 asciidoc은 문서 생성시만 필요함. automotive-dlt는 DLT log messages를 얻고자 하는 경우 필요. build 디렉토리에서 libCommonAPI.so파일이 있는지 확인 할 것.
make install 과 installation prefix를 이용해서 /usr/local 등의 디렉토리에 CommonAPI runtime library를 설치 할 수도 있음. 보통 필요치 않은 경우가 많아서 make install은 빼고 진행함.
step3 : CommonAPI SOME/IP runtime library 빌드 하기
$ git clone https://github.com/GENIVI/capicxx-someip-runtime.git
CommonAPI C++ D-Bus가 D-Bus library인 libdbus을 필요로 하듯이, SOME/IP는 core library인 vsomeip가 필요하다.
$ git clone https://github.com/GENIVI/vsomeip.git
진행하기전에 사전필요 사항이 충족되는지 다시한번 확인 할 것. vsomeip는 일부 core network 기능 구현을 위해서 standard cross-platform C++ library Boost.Asio를 사용함. Boost(1.55 ~ 1.65)가 설치 되어 있는지 확인 할 것.
dpkg -s libboost-dev | grep 'Version'
위 명령으로 boost version확인 가능. 그 다음으로 진행하자.
asciidoc, source-highlight, gtest가 없다고 나오는 경우 설치 해주자.
cd vsomeip
mkdir build
cd build
cmake..make
cmake후에 asciidoc, source-highlight, gtest가 없다고 나오는 경우 설치 해주자.
sudo apt-get install asciidoc
sudo apt-get source-highlight
sudo apt-get install libgtest-dev
GTEST_ROOT가 정의되지 않았다고 나오는 경우는 다음과 같이 하고 다시 cmake
GTEST_ROOT="/usr/src/gtest"export GTEST_ROOT
make까지 별 문제 없이 되긴 하는데, 메뉴얼에는 혹시 모를 문제를 피하기 위해 아래와 같이 cmake 옵션을 넣어 주길 권하고 있다.
cmake -DENABLE_SIGNAL_HANDLING=1 -DDIAGNOSIS_ADDRESS=0x10 ..
make-----
ENABLE_SIGNAL_HANDLING=1은 vsomeip의 signal handling(SIGINT/SIGTERM)을 enable 하는 것으로, vsomeip application을 ctrl-c로 중단 할 수 있다.
두번째 파라메터인 DIAGNOSIS_ADDRESS는 SOME/IP client ID의 첫번째 바이트를 지정하는 것임.(지금 몰라도 된다고 함) client ID는 SOME/IP 메시지 헤더에 있는 숫자로 시스템에서 unique 해야 함. 이 숫자가 unique하다는 것을 보장하기 위해서, 모든 디바이스(노드)들은 vsomeip 구현할 때 이 client ID의 첫번째 byte를 고정값(fixed value)로 설정한다. (SOME/IP 네트워크 상의 모든 디바이스들은 모두 다른 값으로 설정 해야 함.)
이제 CommonAPI C++ SOME/IP runtime library를 빌드 한다.
cd capicxx-someip-runtime
mkdir build
cd build
cmake -DUSE_INSTALLED_COMMONAPI=OFF ..
make
make install은 하지 않는다. libCommonAPI-SOMEIP.so만 build디렉토리에 생겼는지 확인 하자.
step4 : Franca file 작성 및 코드 생성
이제 CommonAPI application(정확하게 말하자면 두개의 applications)을 작성할 준비가 다 되었다. 하나는 sayHello method를 제공하는 서비스, 다른 하나는 이 method를 호출하는 client이다. CommonAPI에서 서비스가 제공하는 인터페이스는 Franca IDL이라는 수단으로 정의 된다. (몰라도 걱정마란다. 금방 배운다고..)
먼저 디렉토리를 생성하고 HelloWorld.fidl 이름의 빈 ASCII파일을 생성한다.
mkdir project
cd project
mkdir fild
cd fidl
vi HelloWorld.fidl
다음의 인터페이스를 생성 하자.
package commonapi
interface HelloWorld {
version {major 1 minor 0}
method sayHello {
in {
String name
}
out {
String message
}
}
}
준비 되었음. 인터페이스 HelloWorld를 갖는 service가 인스턴스 화 되며 이 HelloWorld는 호출이 가능한 sayHello라는 함수를 제공하는 것이다. 다음 단계는 코드를 생성하는 것이며 이를 위해 code generator가 필요하게 된다. (tool repositories에 있음). clone해서 빌드 하면 되는데 Maven이 필요하다. RiseV2G에서 사용했었는데 반갑구만. 암튼 여기서는 그냥 built 된 code generator를 갖다 쓰자고 한다.
https://github.com/GENIVI/capicxx-core-tools/releases 에서 다운 받자. 나는 3.1.10.2 버전으로 받았음.
여기까지는 D-bus와 같다. 하지만 SOME/IP에서는 추가적으로 service와 method identifier정의가 필요하다. Franca IDL에서는 이러한 형태의 IPC framework관련된 파라메터들을 Franca deployment file의 형태로 지원하고 있다. (확장자 .fdepl)보통 deployment specification file이라고도 하는데 CommonAPI C++ SOME/IP표준에 준하여 구현되며 SOME/IP code generator project에서 얻을 수 있다. https://github.com/GENIVI/capicxx-someip-tools 로 가서
CommonAPI-SOMEIP_deployment_spec.fdepl 디렉토리를 참고 할 것. 이 deployment spec.을 기반으로 deployment parameter file을 작성하자.
mkdir project
cd project
mkdir fild
cd fidl
vi HelloWorld.fdelp
다음과 같이 입력 한다.
import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-SOMEIP_deployment_spec.fdepl"
import "HelloWorld.fidl"
define org.genivi.commonapi.someip.deployment for interface commonapi.HelloWorld {
SomeIpServiceID = 4660
method sayHello {
SomeIpMethodID = 123
}
}
define org.genivi.commonapi.someip.deployment for provider MyService {
instance commonapi.HelloWorld {
InstanceId = "test"
SomeIpInstanceID = 22136
}
}
SOME/IP deployment spec.은 몇가지 필수적인 파라메터를 가지고 있는데, 먼저 binding independent CommonAPI deployment spec.을 import한다. 이로서 instance deployment에서 paramenter instance ID를 찾을 수 있다. 다음으로 SOME/IP code generator가 필요하다. 위에서 받은 코드 제너레이터와 다음 링크에서 받은 것을 사용하면 됨. https://github.com/GENIVI/capicxx-someip-tools/releases/tag/3.1.10
commonapi-generator-linux-x86_64 -sk ./fidl/HelloWorld.fidl
commonapi-someip-generator-linux-x86_64 ./fidl/HelloWorld.fdeplcd src-gen/v1/commonapi/src-gen/v1/commonapi$ ls
HelloWorld.hpp HelloWorldSomeIPDeployment.cpp HelloWorldSomeIPProxy.hpp HelloWorldStubDefault.cpp
HelloWorldProxyBase.hpp HelloWorldSomeIPDeployment.hpp HelloWorldSomeIPStubAdapter.cpp HelloWorldStubDefault.hpp
HelloWorldProxy.hpp HelloWorldSomeIPProxy.cpp HelloWorldSomeIPStubAdapter.hpp HelloWorldStub.hpp
Step 5 : client와 server application 작성하기
이제 Hello World application을 작성할 수 있다. 인사한번 하기 힘드네.. project 디렉토리에 src와 build 폴더를 새로 만들고 src폴더로 이동한다.
~/Project/GENIVI/project$ ls
build fidl src
4개의 파일을 생성하는데, client code인 HelloWorldClient.cpp와 service main function을 가지고 있는 HelloWorldService.cpp, 그리고 나머지 2개 파일(header와 source)은 생성된 skeleton code를 구현하기 위한 것이다. (보통 HelloWorldStubImpl.hpp 와 HelloWorldStubImpl.cpp로 부름)
먼저 client code부터 작성해 보자
// HelloWorldClient.cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <CommonAPI/CommonAPI.hpp>
#include <v1/commonapi/HelloWorldProxy.hpp>
using namespace v1::commonapi;
int main() {
std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get();
std::shared_ptr<HelloWorldProxy<>> myProxy = runtime->buildProxy<HelloWorldProxy>("local", "test");
// START modification
// I ran into a NULL myProxy on older versions (possibly a version
// mismatch). Instead of segfaulting, let's note it here. This is the
// only change compared to the original "10 minutes" example
if (!myProxy)
{
std::cerr << "HelloWorldClient FAIL: Returned proxy is NULL! - Giving up!\n";
return 2;
}
// END modification
std::cout << "Checking availability!" << std::endl;
while (!myProxy->isAvailable())
usleep(10);
std::cout << "Available..." << std::endl;
CommonAPI::CallStatus callStatus;
std::string returnMessage;
myProxy->sayHello("Kim", callStatus, returnMessage);
std::cout << "Got message: '" << returnMessage << "'\n";
return 0;
}
CommonAPI application 시작에 있어 runtime object의 pointer를 가져오는게 필요하다. runtime은 proxy와 stub를 생성하는데 필요하다. client application은 service application에 있는 interface의 인스턴스 함수를 호출해야 하는데, 이를 위해서 먼저 client에서는 proxy를 만들어 줘야 한다. interface 이름은 buildProxy method의 template parameter로 지정됨. 추가적으로 특정 인스턴스용으로 proxy를 생성하는데, 이 인스턴스 이름은 buildProxy의 두번째 파라메터로 지정한다. (뭐가 두번짼가? 아마도 test인것 같음. C++을 몰라서. -_-;).. 보통 인스턴스들 사이를 domain이라고 부르는(첫번째 파라메터, 여기서는 local) 값으로 구분하는데, 여기서는 깊이 들어가지는 않고 그냥 항상 "local” domain으로 사용하도록 하겠다. proxy는 isAvailable API 함수를 제공하는데, 처음으로 서비스를 시작하려고 하면 isAvailable 이 항상 True를 리턴해야 한다. callback을 등록하는 것도 가능한데, 여기서는 단순하게 하기 위해 그냥 넘어가겠다. 그 다음으로 sayHello 함수를 호출한다.(fidl 파일에 정의 했었다.) 이 함수는 한개의 입력(string, 여기서는 “Kim”)과 한개의 출력(string, 여기서는 아마도 returnMessage)파라메터를 갖고 있는데, HelloWorldProxy.hpp 를 열어 보면 sayHello 함수를 어떻게 호출해야 하는지 알 수 있다. 주목해야 할 점은 이 함수를 sync 형태로 호출할 수 있고(여기서 우리가 한것 처럼), async 형태로도 호출 할 수가 있다. (좀더 복잡함.) 한 개의 리턴 값은 CallStatus 인데, 이 값은 호출이 성공적인지 아닌지에 대한 정보를 준다. 여기에서는 그냥 callStatus값을 check하지 않고, 잘 호출되었다고 가정한다.
계속 해서 service를 작성해 보자. HelloWorldService.cpp 파일을 작성하자.
// HelloWorldService.cpp
#include <iostream>
#include <thread>
#include <CommonAPI/CommonAPI.hpp>
#include "HelloWorldStubImpl.hpp"
using namespace std;
int main() {
std::shared_ptr<CommonAPI::Runtime> runtime = CommonAPI::Runtime::get();
std::shared_ptr<HelloWorldStubImpl> myService =
std::make_shared<HelloWorldStubImpl>();
runtime->registerService("local", "test", myService);
std::cout << "Successfully Registered Service!" << std::endl;
while (true) {
std::cout << "Waiting for calls... (Abort with CTRL+C)" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(30));
}
return 0;
}
interface 구현이 stub에 있기 때문에 service 함수가 client보다는 훨씬 간단하다. 먼저 runtime 환경을 지정하고, 구현된 stub를 인스턴스화 해서 이 인스턴스를 registerService를 호출을 통해 등록한다. 서비스는 죽이기 전까지는 계속 동작하면서 함수 호출시 마다 응답하게 할 것이므로, main 함수 마지막에 while loop를 돌렸다. 마지막으로 stub를 구현해야 한다. stub 를 구현하는 class는 stub-default로 부터 상속받는다.(src-gen폴더 참고) 헤더 파일은 다음과 같다.
#ifndef HELLOWORLDSTUBIMPL_H_
#define HELLOWORLDSTUBIMPL_H_
#include <CommonAPI/CommonAPI.hpp>
#include <v1/commonapi/HelloWorldStubDefault.hpp>
class HelloWorldStubImpl: public v1::commonapi::HelloWorldStubDefault {
public:
HelloWorldStubImpl();
virtual ~HelloWorldStubImpl();
virtual void sayHello(const std::shared_ptr<CommonAPI::ClientId> _client, std::string _name, sayHelloReply_t _return);
};
#endif /* HELLOWORLDSTUBIMPL_H_ */
실제 구현은 아래의 cpp 파일에 있다.
// HelloWorldStubImpl.cpp
#include "HelloWorldStubImpl.hpp"
HelloWorldStubImpl::HelloWorldStubImpl() { }
HelloWorldStubImpl::~HelloWorldStubImpl() { }
void HelloWorldStubImpl::sayHello(const std::shared_ptr<CommonAPI::ClientId> _client,
std::string _name, sayHelloReply_t _reply) {
std::stringstream messageStream;
messageStream << "Hello " << _name << "!";
std::cout << "sayHello('" << _name << "'): '" << messageStream.str() << "'\n";
_reply(messageStream.str());
};
sayHello 함수가 호출되면 name을 가져와서 앞에다가 "Hello”를 붙여서 리턴한다. 리턴 파라메터는 fidl파일에 정의된 것과는 다르게 바로 string 타입은 아니다. in-parameter로 리턴 파라메터를 가지는 표준 함수 object 형태임. 이렇게 하는 이유는 이 함수 구현에 있어 동기성을 배제하고 다른 thread에서 답을 할 수 있게 하기 위함이다.
step 6 : build 하고 run
Cmake를 설치 했으므로 지금까지 작성한 소스 파일들을 컴파일하고 실행시키는 가장 빠른 방법은 간단한 Cmake 파일을 작성하는 것이다. project 디렉토리 안에 CMakeLists.txt 파일을 다음과 같이 만들자.
cmake_minimum_required(VERSION 2.8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -std=c++0x")
include_directories(
src-gen
$ENV{RUNTIME_PATH}/capicxx-core-runtime/include
$ENV{RUNTIME_PATH}/capicxx-someip-runtime/include
$ENV{RUNTIME_PATH}/vsomeip/interface
)
link_directories(
$ENV{RUNTIME_PATH}/capicxx-core-runtime/build
$ENV{RUNTIME_PATH}/capicxx-someip-runtime/build
$ENV{RUNTIME_PATH}/vsomeip/build
)
add_executable(HelloWorldClient
src/HelloWorldClient.cpp
src-gen/v1/commonapi/HelloWorldSomeIPProxy.cpp
src-gen/v1/commonapi/HelloWorldSomeIPDeployment.cpp
)
target_link_libraries(HelloWorldClient CommonAPI CommonAPI-SomeIP vsomeip)
add_executable(HelloWorldService
src/HelloWorldService.cpp
src/HelloWorldStubImpl.cpp
src-gen/v1/commonapi/HelloWorldSomeIPStubAdapter.cpp
src-gen/v1/commonapi/HelloWorldStubDefault.cpp
src-gen/v1/commonapi/HelloWorldSomeIPDeployment.cpp
)
target_link_libraries(HelloWorldService CommonAPI CommonAPI-SomeIP vsomeip)

※ project 폴더 안에 CMakeLists.txt 파일과 build/ fidl/ src/ src-gen/ 디렉토리가 있는지 확인 할 것
※ ~/Project/GENIVI/project/src-gen/v1/commonapi$ 폴더 안의 HelloWorldSomeIPStubAdapter.hpp 파일을 열어 보면#include <CommonAPI/SomeIP/Config.hpp> 로 되어 있어서 에러가 남. 아래 처럼 파일명을 바꿔준다.
#include <CommonAPI/SomeIP/Configuration.hpp>해당 파일은 아래 위치에 있음.
~/Project/GENIVI/capicxx-someip-runtime/include/CommonAPI/SomeIP$
이제 client와 service를 실행 할 준비가 다 되었다. (build 디렉토리에 실행파일 2개 생성되어 있음 확인 가능) 추가적으로 vsomeip를 위한 configuration file 을 만들어 주는 과정도 필요 할 수 있는데, 이 HelloWorld 예제는 외부 통신이 없는 간단한 예제라 이런 과정이 필요 없음.
HelloWorldServer &
HelloWorldClient → ‘Hello Kim’
인사 한번 하기 힘들다.. -_-;