개발자 가이드

devguide.png

RT컴포넌트 개발자에 적합한 문서. RTC를 만드는 방법 및, RTC를 조합시켜서 시스템을 만드는 방법에 대해서, 「입문편」과 「응용 편」에 나누어서 해설합니다. 또, OpenRTM-aist의보다 상세한 내부구조에 대해서도 해설합니다.

RTC 프로그래밍 입문

RTC프로그래밍의 흐름


RTC프로그래밍의 흐름

OpenRTM-aist는 컴포넌트를 개발하고 싶은 유저(컴포넌트 디벨로퍼)가 가지는 기존의 소프트웨어 자산, 혹은 새롭게 작성한 소프트웨어를 용이하게 RT컴포넌트(RTC)화하기 위한 프레임 워크를 제공합니다. 컴포넌트 작성의 대략의 흐름은 아래의 그림과 같습니다.



ComponentDevelFlow_ko.png
RT컴포넌트의 개발 흐름


컴포넌트 개발자는 기존의 소프트웨어 자산의 라이브러리 함수·클래스 라이브러리 등을 컴포넌트 프레임 워크에 묻어 컴포넌트를 작성합니다. 이렇게 하는 것으로 기존의 소프트웨어 자원을 소프트웨어 부품인 RT컴포넌트로서 작성해 두어, 여러가지 장면에서 재이용할 수 있게 됩니다. 작성된 RT컴포넌트는, 네트워크상의 적절한 장소에 배치하고, 분산 오브젝트로서 네트워크상의 임의의 장소로부터 이용할 수 있습니다.

그림에 나타내도록(듯이), RT컴포넌트 체제에 준거해 작성된 RT컴포넌트는 크게 나누어 2개의 바이노리필드로서 작성할 수 있습니다.스탠드얼론 RT컴포넌트 (Standalone RT-Component)는, 단일 파일로 그대로 실행할 수 있는 실행 형식의 바이너리입니다. loadable module RT컴포넌트 (Loadable Module RT-Component)는 동적으로 로드 가능한 loadable module 형식의 바이노리필드입니다. RT컴포넌트는 이러한 2개의 형식에서 작성, 배포, 실행할 수 있습니다.

RTC프로그래밍의 기초

통상의프로그래밍과 RT컴포넌트의프로그래밍에는, 몇개의 큰 차이가 있습니다.

main 함수가 없는 프로그램

RT컴포넌트의 프로그램에는 통상의 프로그램과는 달라, main 함수가 없습니다. 대신에 1개의 RT컴포넌트는, 통상 있는 특별한 base class을 계승한 하나의 클래스로서 실장됩니다.

RT컴포넌트에 시키고 싶은 처리는, 그 base class의 멤버 함수(메소드)를 오버라이드(override) 하는 형태로 기술합니다. 예를 들면, 초기화와 같은 처리는, onInitialize라고 하는 함수안에 기술합니다.혹은, 종료시에 실시하고 싶은 처리이면, onFinalize 라고 하는 함수안에 기술합니다.

 ReturnCode_t MyComponent::onInitialize()
 {
   // 초기화 처리 등
 }
 ReturnCode_t MyComponent::onFinalize()
 {
   // 종료 처리 등
 }

그럼, 여기서 쓴 초기화 처리나, 종료 처리는 언제 실행되는 것입니까? 그것을 알기 위해서는, RT컴포넌트의 라이후사이크루를 알 필요가 있습니다.

컴포넌트·라이후사이크루

어느 RT컴포넌트가 태어나고 나서 죽을 때까지의 일련의 흐름을, 컴포넌트의 라이후사이크루라고 부릅니다.

컴포넌트에는 기본적으로

  • 생성 상태(Created)
  • 활동 상태(Alive)
  • 종료 상태

의 3개 상태를 가집니다. (Alive 상태는 내부에 한층 더 상태를 가집니다(후술).)

컴포넌트는 1개의 클래스인 것은 위에서 말했습니다. 따라서, 컴포넌트가 생성되는 것은, 오브젝트(인스턴스)가 생성되는 것으로 거의 같습니다. 통상, RT컴포넌트는 매니저(RTC 매니저)에 의해서 생성되어 이후 매니저가 RT컴포넌트의 라이후사이크루를 관리합니다.

구체적으로는, 매니저는 RT컴포넌트의 인스턴스 생성 후, 위에서 말한 onInitialize 함수를 콜 합니다. 또, RT컴포넌트가 종료할 때, 매니저는 onFinalize 함수를 콜 합니다. 이와 같이, RT컴포넌트의 라이후사이크루안의 특정의 타이밍에 할당할 수 있었던 처리(이것을 액션이라고 부른다) 마다, 필요한 처리를 기술하는 것으로, RT컴포넌트의프로그래밍을 실시합니다.

실행 문맥

통상 프로그램을 실행하면 스렛드를 할당할 수 있어 그 스렛드가 프로그램으로서 기술된 처리를 실행합니다. 로봇을 제어하는 프로그램에서는, 통상 스렛드에 의해 실행되는 루프(제어 루프나 처리 루프)를 가져, 센서 데이터를 처리하거나 액츄에이터를 계속 제어합니다. 이러한, 무엇인가를 처리하거나 제어하거나하기 위한 주된 처리를 RT컴포넌트에서는 코어 논리라고 부릅니다.

RT컴포넌트는, 생성되어 Alive 상태가 되면 통상 하나의 스렛드를 할당할 수 있어 RT컴포넌트로서의 메인의 처리(코어 논리)를 실행합니다. 이 스렛드를 RT컴포넌트에서는 실행 문맥(ExecutionContext)이라고 부릅니다. 실제로는, 실행 문맥은 스렛드 그 자체가 아니고, 스렛드를 추상적으로 표현한 것으로 실행 주기나 상태를 가집니다. 즉, RT컴포넌트가 생성되면 실행 문맥이 RT컴포넌트에 관련지을 수 있어 코어 논리가 구동되는 것으로, RT컴포넌트가 어떠한 처리(예를 들면 로봇을 제어하는 등)를 실시합니다.

RTC 상태 천이

위에서 말한 것처럼, RT컴포넌트는 상태를 가져, 그 상태나 천이에 할당할 수 있었던 액션으로서 처리를 기술합니다. 밑그림은 RT컴포넌트 상태 천이도(UML의 스테이트 머신도)를 나타내고 있습니다.



RTCStateMachine040_ko.png
RT컴포넌트 상태 천이



Created와 Alive는 RT컴포넌트 상태입니다. Alive 상태안에도 몇개의 상태가 존재하고 있습니다.

스렛드의 정지상태와 실행 상태

우선, Alive 상태 내부의 상부의 Stopped와 Running 상태에서 보고 갑니다.



RTCStateMachineStartStop_ko.png
스렛드의 정지상태와 실행 상태



이것은, 실행 문맥을 스렛드로서 보았을 때, 스렛드가 정지중(Stopped)이나 실행중(Running)인지를 나타내는 상태입니다.

정지상태(Stopped)에 있는 실행 문맥이 start 이벤트를 받으면, RT컴포넌트의 onStartup를 실행해 실행 상태(Running)에 천이 합니다. 반대로 stop 이벤트에 의해, 실행 문맥은 RT컴포넌트의 onShutdown를 실행해 정지상태(Stopped) 상태에 천이 합니다.

코어 논리의 액션은, 실행 상태(Running) 상태 때 마셔 실행되어 정지상태에 대해 모든 액션은 실행되지 않습니다.

액티브·비액티브 상태

Alive 상태내의 하단은 코어 논리의 액티브(Active)·비액티브(Inactive)·에러(Error)에 관한 상태 천이입니다.

RT컴포넌트 생성 직후는, RT컴포넌트는 비액티브 상태(Inactive)에 있습니다. RT컴포넌트를 액티브화하면, RT컴포넌트의 액션인 onActivate가 콜 되어 액티브 상태에 천이 합니다. 액티브 상태에 있는 동안, 통상 RT컴포넌트의 액션 onExecute가 반복해 실행계속 됩니다. 통상은 이 onExecute내에서, RT컴포넌트의 메인의 처리를 실시합니다. 예를 들면, 센서로부터 데이터를 읽어들여 다른 컴포넌트에 보내거나 다른 컴포넌트로부터 받은 데이터에 근거해 모터를 제어하거나라고 한, 로봇에 있어 기본적인 반복 처리는 onExecute에 기술하게 되겠지요.

RT컴포넌트는 비액티브화 되는지, 에러가 발생할 때까지 액티브 상태에 계속 머뭅니다. 비액티브화 되는 경우는 onDeactivate가 콜 되어 비액티브 상태에 천이 합니다. 액티브 상태의 처리 중(안)에서 어떠한 에러가 발생했을 경우, RT컴포넌트의 액션인 onAborting가 콜 되어 에러 상태(Error)에 천이 합니다.

에러 상태에 천이 했을 경우, 외부로부터 리셋트를 할 때까지 에러 상태에 계속 머물어 onError가 콜 계속 됩니다. 리셋트를 하면, onReset가 콜 됩니다. onReset의 처리가 성공하면 비액티브 상태(Inactive)에 천이 해, 다시 액티브 상태가 될 수 있습니다만, onReset가 실패했을 경우는, 에러 상태에 계속 머뭅니다.



RTCStateMachineActiveInactive_ko.png
비액티브 상태·액티브 상태·에러 상태



액션의 정리

RT컴포넌트 개발자의 주된 일은, 자신이 작성하려고 하는 컴포넌트에서는, 지금까지 말해 온 RT컴포넌트의 각 상태마다 어떤 처리를 하면 좋은 것인지를 생각해 각각의 액션에 대응하는 함수를 실장하는 것입니다. 즉, 자신이 작성하는 컴포넌트에 필요한on???라고 하는 함수만을 오버라이드(override) 해, 함수의 내용을 기술하면 좋습니다.

이하에, 컴포넌트의 액션의 함수로 역할을 나타냅니다.

onInitialize 초기화 처리.콘포넨트라이후사이크루의 개시시에 한 번만 불린다.
onActivated 비액티브 상태로부터 액티브화 될 때 1도만 불린다.
onExecute 액티브 상태시에 주기적으로 불린다.
onDeactivated 액티브 상태로부터 비액티브화 될 때 1도만 불린다.
onAborting ERROR 상태에 들어가기 전에 1도만 불린다.
onReset 에러 상태로부터 리셋트 되어 비액티브 상태로 이행할 때 1도만 불린다.
onError 에러 상태에 있는 동안 주기적으로 불린다.
onFinalize 콘포넨트라이후사이크루의 종료시에 1도만 불린다.
onStateUpdate onExecute의 뒤매회 불린다.
onRateChanged ExecutionContext의 rate가 변경되었을 때 불린다.
onStartup ExecutionContext가 실행을 개시할 때 1도만 불린다.
onShutdown ExecutionContext가 실행을 정지할 때 1도만 불린다.

데이터포트 (기초편)

데이터포트란

데이터포트는 주로 연속적인 데이터를 RTC간에 주고받는하기 위한 포토입니다. 데이터를 다른 RTC에 송신하기 위한 데이터포트를 OutPort, 다른 RTC로부터 데이터 를 수신하기 위한 데이터포트를 InPort라고 부릅니다.「InPort」, 「OutPort」 를 정리해 「데이터포트 (DataPort)」라고 부르는 일이 있습니다.

dataport_ko.png
데이터포트 (InPort와 OutPort)

RTC는 여러가지 프로그램 언어로 기술할 수 있습니다.또, RT컨포넌트 는 네트워크상에 분산시키는 일도, 같은 노드상에 배치 , 혹은 같은 프로세스상에 둘 수도 있습니다.그리고, 양단의 RTC가 어떤 언어로 기술되고 있는지, 네트워크적으로 분산하고 있는 것에 관계없이, 데이터포트간의 데이터의 통신은 투과적으로 행해집니다.

RTC는 필요에 따라서 임의의 수의 데이터포트를 갖게할 수 있습니다.예를 들면, 센서로부터 데이터를 취득하는 컴퍼넌트를 만든다고 합니다.이 컨포넌트 는 적어도 하나의 센서 데이터를 출력하기 위한 OutPort가 필요하게 되는 것으로 구성됩니다.

혹은, 지정된 토크에 따라서, 모터를 구동하는 컴퍼넌트를 작성합니다. 이 컴퍼넌트는, 적어도 하나의 하나의 토크치 지령을 수 취하는 InPort가 필요하게 됩니다. 이러한 컴퍼넌트를 이용하고, 피드백 제어를 행하기 위한 제어기 (콘트롤러) 컴퍼넌트를 만든다고 한다면 센서 데이터를 받는 InPort, 지령치 (예를 들면 속도 지령)를 받는다 InPort, 토크치를 출력하는 OutPort의 각각이 필요하게 됩니다.

dataport_example_ko.png
센서, 콘트롤러, 모터와 데이터포트의 예

프로그램으로서 실제로 InPort와 OutPort를 이용하는 간단한 예를 보겠습니다.각 오브젝트는 각각 이하의 기능을 합니다.

  • encoderDevice: 하드웨어 (예를 들면 카운터 보드등 )를 제어해 엔코더로부터 현재의 각도를 읽어내기 위한 기능이 실장된 오브젝트.하드웨어 벤더가 그러한 라이브러리등을 제공하고 있지 않으면, 스스로 실장할 필요가 있다.
  • encoderData: OutPort 용으로 엔코더의 데이터를 보관 유지하는 변수.여기에서는, 엔코더의 값을을 보관 유지하는 data 라고 하는 필드 (구조체의 멤버)를 가지고 있는 것으로 한다.
  • encoderDataOut: OutPort 오브젝트.encoderData 오브젝트에 관련지을 수 있고 있다.

 // 엔코더 컴퍼넌트의 예
 encoderData.data = encoderDevice.read(); // 카운터로부터 현재가를 취득
 encoderDataOut.write();                  // OutPort 로부터 데이터가 나간다

1행째로는, encoderDevice 오브젝트의 read() 함수를 부르고, 엔코더 의 현재가를 읽어들이고 있습니다.읽힌 데이터는, encoderData 오브제 쿠트의 data 멤버에게 대입됩니다.OutPort 의 인스턴스이다 encoderDataOut 오브젝트는, write()가 불리면, encoderData 오브제 쿠트로부터 데이타를 뽑기 시작해, 접속되고 있는 InPort 에 데이터를 출력합니다.

한편, InPort를 가지는 모터 컴퍼넌트는, 이하와 같이 쓸 수 있습니다.

  • motorDevice: 하드웨어(예를 들면 모터 드라이버에 접속된 DA보드등 )를 제어해 모터 제어를하기 위한 오브젝트.벤더로부터 그러한 라이브러리가 제공되어 있지 않으면, 스스로 실장할 필요가 있다.
  • motorData: InPort 로부터 입력된 값을 보관 유지하는 변수.여기에서는, 엔코더의 값을을 보관 유지하는 data 라고 하는 필드(구조체의 멤버)를 가지고 있는 것으로 한다.
  • motorDataIn: InPort 오브젝트.motorData 오브젝트에 관련지을 수 있고 있다.

 // 모터 컴퍼넌트의 예
 if (motorDataIn.isNew() {
   motorData.data = motorDataIn.read(); // InPort로부터 데이터를 읽는다
   motorDevice.output(motorData.data);  // 모터 드라이버에 지령치를 출력
 }

1행째로는 우선 InPort에 데이터가 와있을지 확인하고 있습니다.데이터가 도 착용하고 있으면, motorDataIn 의 read() 함수를 부르고, InPort로부터 데이터를 motorData 의 data 멤버에게 읽어들이고 있습니다.다음에, 실제로 모터에 지령 값을 건네주기 위해, motorDevice 오브젝트의 output 함수를 호출하고 있습니다.

같이 InPort와 OutPort를 가지는 제어기 컴퍼넌트에서는 이하와 같이 되는 것으로 짊어진다.

 // 제어기 컴퍼넌트의 예
 if (positionDataIn.isNew() && referenceDataIn.isNew()) {
 
   positionDataIn.read();  // 위치 데이터를 InPort로부터 읽어들인다
   referenceDataIn.read(); // 속도 지령을 InPort로부터 읽어들인다
 
   // 제어 알고리즘에 따라서 모터에게 주는 토르크치를 계산
   torqueData.data = controller.calculate(positionData.data,
                                           referenceaData.data);
   torqueDataOut.write(); // 모터 토르크치를 OutPort로부터 출력
 }

가고 있는 것은, 각각 InPort, OutPort만의 경우와 그만큼 변화키 응의 것으로, 자세한 설명은 생략 합니다.상대의 RTC가 어느 언어로 쓰여져 있는지, 아 있어는, 네트워크상의 다른 노드상에 있는지 로컬에 있는지 등의 위 있어에 대해서는, RT컴퍼넌트 체제에 의해 은폐 되고 있으므로, 이와 같이 간단하게 데이터의 송수신을 실시할 수 있습니다.

변수의 형태

여기까지의 예에서는, 각 오브젝트의 선언이 나타나지 않기 때문에, C++나 Java등 형태가 있는 언어에 익숙해 있는 분은, 샘플 프로그램의 각 변수가 어떠한 형태 인가 신경이 쓰였을지도 모릅니다.

기본형

위의 예의 데이터 격납 변수로 상정하고 있던 것은, TimedDouble 라고 하는 데이터형 입니다.C/C++의 구조체로 쓰면, 거의 이하와 같은 구조체와 동등의 것입니다.

 struct Time
 {
   long int sec;
   long int usec;
 };
 
 struct TimedDouble
 {
   Time tm;
   double data;
 };

데이터포트의 형태에 관해서는, 이하와 같은 결정이나 특징이 있습니다.

  • 데이터포트에는 각각 특유의 형태가 있다.
  • 형태의 정의는 IDL(Interface Definition Language)라고 하는 언어비의존의 인터페이스 정의 언어에 의해서 정해져 있다.
  • 언어가 달라도, IDL 정의의 형태가 같으면 접속할 수 있다.
  • 형태가 다른 데이터포트끼리는 접속하지 못하고, 데이터의 송수신은 실시할 수 없다.

따라서, 상기의 예로, 엔코더, 제어기, 모터의 각 컴퍼넌트를 접속 하기 위해서는, 포토의 데이터형이 각각 TimedDouble 형 나오지 않으면 되어 선.

덧붙여 OpenRTM-aist 에서는, 디폴트로 이하와 같은 데이터포트형을 준비해 (이)라고 내려 특히 정의하는 일 없이 이용할 수 있습니다.이러한 디폴트 정의의 기본형에는 타임 스탬프 보관 유지용으로 tm 필드가 준비되어 있습니다.

형명 내용
TimedShort 타임 스탬프와 short int 형
TimedUShort 타임 스탬프와 unsigned short int 형
TimedLong 타임 스탬프와 long int 형
TimedULong 타임 스탬프와 unsigned long int 형
TimedFloat 타임 스탬프와 float 형
TimedDouble 타임 스탬프와 double 형
TimedString 타임 스탬프와 string 형
TimedWString 타임 스탬프와 wstring 형
TimedChar 타임 스탬프와 char 형
TimedWChar 타임 스탬프와 wchar 형
TimedOctet 타임 스탬프와 아르바이트형
TimedBool 타임 스탬프와 bool 형

이러한 쳐, TimedChar, TimedWChar, TimedOctet 는 너무 사용하는 장면은 없을지도 모릅니다.

IDL형으로부터 각 언어 고유에의 대응 관계를 매핑이라고 합니다.각각의 형태 (으)로부터 각 언어상의 형태에의 매핑은 CORBA의 언어 매핑 시방서 또는 「언어 매핑」의 장을 참조해 주세요.

조금 복잡한 데이터형

상기의 기본형에는, Seq 라고 하는 순서형으로 불리는 형태가 준비되어 있습니다. 이것은 간단하게 말하면 배열을 보관 유지할 수 있는 형태입니다.

 seqdata.length(10); // 배열을 10개분확보한다
 for (int i(0); i < seqdata.length(); ++i) // 인수 없음 length 는 길이를 돌려준다
 {
   seqdata[i] = i; // 대입한다
 }

C++에서는 이와 같이 이용할 수 있습니다.배열보다는 편리하고, STL의 vector (을)를 닮아 있습니다만, vector보다는 많이 저기능입니다.Java에서는 배열 전용의 홀더 클래스가 자동적으로 생성되어 이것을 이용할 수 있습니다.또, Python에서는, Python의 배열에 직접 매핑 됩니다.

방금전의 예에서는, 엔코더와 모터는 하나였지만, 실제의 로봇에서는 다 구의 자유도를 취급할 필요가 있습니다.그 때에, 각자사정 때마다 포토를 마련한다 의는, 통신 효율, 동기의 문제등에서 유리한 계책이 아닙니다.그러한 경우로 (은)는, 이러한 순서형을 이용하는 것으로, 복수의 데이터를 효율적으로 취급하는 와 (이)가 할 수 있습니다.

독자적인 데이터형

게다가 더 복잡한 데이터 구조를 취급하고 싶은 경우도 있습니다.그 경우는, 자 분에 데이터형을 정의하고, 데이터포트로 이용할 수도 있습니다.자세한 것은 「데이터포트(응용편)」를 참조해 주세요.

데이터포트의 접속

연결기

RTC가 가지는 InPort와 OutPort를 접속하려면 , RTSystemEditor 나 rtcshell 등 의 툴을 사용합니다.포토를 접속하면 OutPort로부터 송신된 데이터는, 네트워크등을 경유해 InPort에 의해서 수신됩니다.접속은, 시스템의 구조나 컴퍼넌트의 특성에 따르고, 이하와 같이 몇개의 종류를 선택 일이 생깁니다.

  • 데이터 플로우형
  • 인터페이스형
  • 예약 구독형
  • 데이터 송신 폴리시

인터페이스형

인터페이스형에서는, 데이터를 어느 프로토콜로 송수신 하는지를 지정 섬 .디폴트에서는, corba_cdr형이라고 하는 방법만 이용할 수 있게 되어 있어 통상은 이것을 이용하면 특히 문제 없습니다.다만, 시스템의 구성에 (이)라고는, 다른 인터페이스형을 이용하도록(듯이), 확장하는 것도 가능합니다.

cneter
인터페이스형

데이터 플로우형

데이터의 송수신의 방법에는, OutPort 가 InPort 에 데이터를 보내는 push 형의 것도 의와 반대로 InPort 로부터 OutPort 에 문의해 데이타를 뽑아 오는 pull 형 의 것이 있습니다.

push 형에서는, OutPort측의 컴퍼넌트의 주로 액티버티가 주체가 되어 데이터를 수신 측에 보냅니다.보내는 타이밍은 다음의 예약 구독형으로 손가락 정합니다.한편, pull 형에서는, InPort측의 컴퍼넌트의 주로 액티버티 하지만 주체가 되어 데이터를 수신 측에 보냅니다.데이터를 수신하는 타이밍은, InPort측이 read()를 읽은 시점됩니다.

cneter
데이터 플로우형

예약 구독형

예약 구독형은, 데이터 플로우형이 push 때에만 유효한 프로파 티입니다.디폴트에서는, 동기형 송신 방식의 flush, 및 비동기형 송신분 식의 new, periodic 의 3 종류가 제공되고 있습니다.

flush 형은 OutPort로부터 InPort에 데이터를 push 할 때, OutPort의 write 함수내 그리고 직접 데이터의 송신을 실시합니다.즉, write() 함수로부터 돌아왔을 때에는, InPort에 데이터가 도착해 있는 것이 보증됩니다.한편, 상대편의 InPort가 네트워크적으로 먼 장소에 있어, 통신에 시간이 걸리는 경우에는, write()로 긴 시간 기다리게 될 가능성이 있습니다.따라서, 예를 들면 액티버티의 논리를 리얼타임 실행하고 싶은 경우에는 flush 형에서는 문제가 생기는 경우가 있습니다.

new 형과 periodic 형에는, publisher 라고 하는 송신을 위한 스렛드가 접속마다 에 준비됩니다.이러한 타입에서는, OutPort 의 write() 함수를 부르면, 데이 타는 일단 버퍼에 기입 희귀 write() 함수는 곧바로 종료합니다.데이터의 열매 때의 송신은, publisher의 별스렛드가 실시합니다.

cneter
예약 구독형

new 형은 기입과 동시에 송신 기다려 하고 있는 publisher 에 대해서 시그널을 송 , 일으켜진 publisher 스렛드가 실제의 데이터 송신을 실시합니다.버퍼 에의 데이터의 기입 주기에 대해서, 데이터 송신 시간이 충분히 짧으면, flush 와 거의 같습니다만, 데이터 송신에 시간이 걸리는 경우에는, 반드시 방법 (이)라고의 데이터가 수신 측에 닿는 것은 아닌 것에 주의해 주세요.그렇게 말했다 의미로 new 형은 best effort적인 데이터 송신 방법입니다.

한편 periodic 형은, publisher 가 일정 주기에 버퍼로부터 데이타를 뽑아 내밀기 데이터 송신을 실시합니다.송신 주기는, 접속시에 외부로부터 줄 수 있습니다. 데이터 송신 주기에 비해, 데이터 송신 시간이 긴 경우, 송신 주기를 지켜지지 않는다 가능성이 있습니다.또, 데이터를 버퍼에 쓰는 주기 (액티버티 의 주기)와 버퍼로부터 데이타를 뽑기 시작해 송신하는 주기 (publisher 의 주 기), 및 후술 하는 데이터 송신 폴리시의 정합성을 고려하지 않으면, 정상적으로 버퍼 풀 상태 또는 밧파엔프티 상태를 일으킬 가능성 .이른바 , 생산자·소비자 문제를 고려할 필요가 있는 접속 타입에 .

데이터 송신 폴리시

예약 구독형이, new 또는 periodic 의 경우, OutPort 는 버퍼 (을)를 가집니다.데이터를 송신하는 타이밍으로, 버퍼에 모여 있는 데이터 (을)를 어떠한 방침으로 송신할까를 데이터 송신 폴리시라고 부릅니다.

데이터 송신 폴리시에 있어는, 버퍼에 보관 유지되고 있는 데이터를 모두 송신

 ''all'', 선입처이고 방식으로 한개씩 송신하는 ''fifo'', 버퍼에 보관 유지되어
(이)라고 있는 데이터를 몇개인가 걸러서 송신하는 skip, 그리고 최신치만 송신해, 외 데이터는 모두 버리게 되는 new의 4 종류가 있습니다.

폴리시명 의미
all 버퍼에 남아 있는 데이터를 모두 송신
fifo 선입처이고 방식으로, 데이터를 한개씩 송신
skip n 개 두어에 데이터를 송신해, 그 이외는 버린다
new 최신치만 송신해, 낡은 값은 버린다

예약 구독형을 new 나 periodic 등의 비동기형으로 했을 경우, 데이터의 생성 속도, 소비 속도, 한층 더 통신로의 대역폭을 사전에 추측한 데다가, 이것 들의 폴리시를 적절히 설정할 필요가 있습니다.

InPort프로그래밍

여기에서는 실제의 프로그램으로 데이터포트가 어떻게 사용되는지를 보고 삽니다.

InPort를 사용할 때 , 이하의 룰을 염두에 둔 위에프로그래밍 한다 일을 추천 합니다.

  • 데이터는 와있지 않을지도 모른다고 해 처리한다
  • 데이터는 올바르지 않을지도 모른다고 해 처리한다
  • 배열의 길이는 항상 변화할지도 모른다고 해 처리한다
  • 데이터는 도중부터 오지 않게 될지도 모른다고 해 처리한다

InPort 에 접속되는 OutPort 는 다른 노드의 RTC의 OutPort일지도 모릅니다. 포토는 접속되어 있지 않을지도 모르고, 데이터를 보내있어일지도 ‚돼 응.배열이 포함되는 데이터형의 경우, 배열의 길이는 다음의 데이터에서는 변화할까 도 알려지지 않습니다.또, 네트워크 접속이 끊어지거나 상대의 RTC가 정지해 섬 경우, 도중부터 데이터를 보내지 않게 될지도 모릅니다.

모듈화하는데 있어서, 가정이나 전제 조건을 줄여, 다른 요소에 의존하지 않아 게 만드는 것은 매우 중요하고, 이것에 의해서 재이용성이 높게 사용하기 쉽다 모듈이 될지가 바뀌어 와 버립니다.

그런데, InPort의 실제의 사용법을 보고 가기 전에, InPort의 구조를 설명합니다.

cneter
InPort의 구조

InPort 의 실체는 오브젝트입니다.C++에서는, 클래스 템플릿 InPort<T> 형태로서 정의되고 있습니다.T에는 데이터포트가 사용하는 데이터형이 들어갑니다. 아래의 예는, 샘플에 부속되어 있는 ConsoleOut 컴퍼넌트의 InPort 선언의 예입니다.InPort 가 TimedLong 형으로 선언되고 있는 것을 알 수 있습니다.

  TimedLong m_in;
  InPort<TimedLong> m_inIn;

선언이나 초기화는, RTCBuilder나 rtc-template를 사용하고 있으면 자동적으로 기술해 줍니다.InPort를 사용할 때 , InPort 오브젝트에 연결시킬 수 있었던 T 형태의 변수가 하나 정의됩니다.방금전의 예로, TimedLong 형의 m_in 라고 하는 것도 의가 그 변수입니다.이것을 InPort 변수라고 부릅니다.

InPort 와 InPort 변수는 초기화시에 관련지을 수 있어 InPort의 데이터 읽기관 수 read()를 부르면, InPort가 가지는 버퍼로부터 데이터가 하나 읽어내져서 InPort 변수에 카피됩니다.InPort 에 온 데이터를 사용할 때 이와 같이 InPort 변수를 개입시켜 이용합니다.

InPort 오브젝트

InPort 클래스 템플릿으로 정의되고 있는 함수를 이하의 겉(표)에 나타냅니다.

이것은 C++의 InPort 클래스의 함수입니다만, 다른 언어에 대해도 거의 동일한 이름으로 각 함수가 제공되고 있습니다.덧붙여 이러한 함수의 참조 설명서는, Windows 에서는, 「스타트」->「OpenRTM-aist」->「C++」->「documents」-> 「Class reference」에서 볼 수 있습니다.Linux 등에서는 문서가 이 스토르 되고 있으면, ${prefix}/share/OpenRTM-aist/docs/ClassReference 등으로부터 액세스 하는 것 하지만 할 수 있습니다.메뉴얼은 doxygen 형식에서 기술되고 있어 상부 메뉴의 「네 무스페이스」로부터 클래스 일람을 표시시켜, InPort를 참조해 주세요.

InPort (const char *name, DataType &value) constructor   
~InPort (void) 소멸자
const char * name () 포토 명칭을 취득한다.
bool isNew () 최신 데이터가 존재하는지 확인한다
bool isEmpty () 버퍼가 하늘인지 어떤지 확인한다
bool read () DataPort 로부터 값을 읽어낸다
void update () 바인드 된 T 형의 변수에 InPort 버퍼의 최신치를 읽어들인다
void operator>> (DataType &rhs) T 형의 데이터에 InPort 의 최신치 데이터를 읽어들인다
void setOnRead (OnRead< DataType > *on_read) InPort 버퍼에 데이터 읽기시의 콜백의 설정
void setOnReadConvert (OnReadConvert< DataType > *on_rconvert) InPort 버퍼에 데이터 읽기시의 콜백의 설정

주로 사용하는 함수는, isNew() 및 read() 함수가 됩니다.실제로 사용되어 (이)라고 있는 예를 보겠습니다.

 RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id)
 {
   if (m_inIn.isNew())
     {
       m_inIn.read();
       std::cout << "Received: " << m_in.data << std::endl;
       std::cout << "TimeStamp: " << m_in.tm.sec << "[s] ";
       std::cout << m_in.tm.nsec << "[ns]" << std::endl;
     }
   return RTC::RTC_OK;
 }

m_inIn.isNew()로 데이터가 와있는지를 확인해, m_inIn.read()로 InPort 변수 m_in 에 데이터를 읽어들이고 있습니다.그 후, m_in의 내용을 cout 로 표시하며 있어 .

통상은, 이 예의 같게 InPort 의 데이터의 처리는, onExecute() 함수내에서 행 있어, InPort에 오는 데이터를 주기적으로 처리하도록(듯이) 프로그램 합니다.

다른 함수는 설명으로부터 곧바로 안다고 생각합니다만, 코르박크오브제크트섹 트 하는 함수 setOnRead 와 setOnRedConvert 에 대해서는, 응용편으로 재차 설명 합니다.

OutPort

OutPort는 InPort와 비교하면, 단지 자신이 데이터를 송낼 뿐입니다 것으로, 조금 간단하게 됩니다.

cneter
OutPort의 구조

구조는 InPort와 거의 같고, C++이면, OutPort는 T형의 형태 인수를 취하는 곳간 스텐프레이트가 되어 있습니다.T는 OutPort의 데이터형으로, 이 T가 같다 InPort에 대해서 밖에 데이터를 보낼 수 없습니다.OutPort도 InPort 같이, OutPort 변수와 함께 이용합니다.OutPort 변수에 데이터를 쓴 후, OutPort의 write() 함수를 부르면 데이터가 OutPort로부터 접속되고 있는 InPort에 배웅해집니다.

OutPort 오브젝트

OutPort 클래스 템플릿으로 정의되고 있는 함수를 이하의 겉(표)에 나타냅니다.

OutPort 에 대해서도 InPort 같이, 다른 언어에 대해도 거의 동일한 이름으로 각 관 수가 제공되고 있습니다.참조 설명서에 대해서도 InPort 같이, doxygen 의 「네임 스페이스」로부터OutPort를 봐 주세요.

OutPort (const char *name, DataType &value) constructor   
~OutPort (void) 소멸자
bool write (DataType &value) 데이터 기입
bool write () 데이터 기입
bool operator<< (DataType &value) 데이터 기입
DataPortStatus::Enum getStatus (int index) 특정의 연결기에의 써 스테이터스를 얻는다
DataPortStatusList getStatusList () 특정의 연결기에의 써 스테이터스 리스트를 얻는다
void setOnWrite (OnWrite< DataType > *on_write) OnWrite 콜백의 설정
void setOnWriteConvert (OnWriteConvert< DataType > *on_wconvert) OnWriteConvert 콜백의 설정

OutPort로 주로 사용하는 함수는 write()와 getStatusList()가 됩니다.

 RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id)
 {
   std::cout << "Please input number: ";
   std::cin >> m_out.data;
   if (!m_outOut.write())
     {
       DataPortStatusList stat = m_outOut.getStatusList();
 
       for (size_t i(0), len(stat.size()); i < len; ++i)
         {
           if (stat[i] != PORT_OK)
             {
               std::cout << "Error in connector number " << i << std::endl;
             }
         }
     }
   return RTC::RTC_OK;
 }

우선, std::cin >> m_out.data 로 표준 입력으로부터 OutPort 에 데이터를 대입 섬 .그 후, m_outOut.write()로 데이터를 OutPort로부터 배웅하고 있습니다.려 치가 false 의 경우, 포토의 스테이터스를 조사해 어느 접속으로 에러가 일어나 있는지를 표시하고 있습니다.

데이터포트의 정리

여기에서는, 데이터포트 (InPort, OutPort)의 기본적인 개념과 사용법에 대해 해설했습니다.데이터포트의 선언은, RTCBuilder나 rtc-template로 가 간다 가, 실제로 어떻게 데이터를 주는지, 혹은 이용하는지에 개 있고는 컴퍼넌트 개발자가 기술할 필요가 있습니다.다만, 간단하게 사용 하는 것 뿐이라면, InPort에서는, isNew()와 read() 함수만, OutPort 에서는, write()와 getStatusList() 함수만 기억해 두면 충분하겠지요.

서비스 포트 (기초편)

서비스 포트란?

컴포넌트 지향 로봇 시스템을 구축하기 위해서는 컴포넌트 간 데이터 통신만으로는 충분하지 않고 커멘드 레벨(혹은 함수 레벨) 의 컴포넌트 간 통신이 필요하게 됩니다. 예를들면 로봇 암을 제어 하는 머니퓰레이터 컴포넌트의 경우, 손끝의 위치나 속도 등은 상위의 어플리케이션이나 컴포넌트에서 데이터 포트에 보내져야 하는 데이터입니다.

한편, 로봇 암의 각종 설정, 좌표계의 설정, 제어 파라메터의 설정, 동작 모드의 설정 등을 데이터 포트에서 하는 것은 적절하다고는 할 수 없고, 객체 지향적으로 한다면 머니퓰레이터 오브젝트에 대해 setCoordinationSystem(), setParameter(), setMode() 등의 함수가 준비되어 있고 이 함수들을 필요에 따라 적절한 타이밍에 호출하는 것이 적절하다고 할 수 있습니다.

serviceport_example_ko.png
서비스 포트의 예

서비스 포트는 이러한 커멘드 레벨의 컴포넌트 간의 주고 받음을 하기 위한 구조를 제공합니다.

일반적으로 서비스란, 기능적으로 관련이 있는 한묶음의 커멘드(함수, 메소드, 오퍼레이션 등이라고도 불리는)군으로 OpenRTM-aist에 있어서는 이 기능을 제공하는 측을 서비스 프로바이더(인터페이스), 기능을 이용하는 측을 서비스 컨슈머(인터페이스)라고 부릅니다.

또한, UML등의 규약에 있어서는 서비스 프로바이더를 Privided Interface, 또한 서비스 컨슈머를 Required Interface 등이라고 부르고 각각 아래의 그림 과 같이 기호(롤리팝(lollipop), 소켓(socket))로 나태냅니다. 이것은 일반적인 용어 및 기술법이므로 기억해두는 편이 좋습니다. 호출 혹은 호출되는 방향으로 한다면, 호출되는 쪽이 프로바이더(Provided Interface)이고, 호출하는 쪽이 컨슈머(Required Interface)라고 볼 수 있습니다.

provider_and_consumer_ko.png
프로바이더와 컨슈머

  • 프로바이더 (Provided Interface): 호출되는 측, 서비스를 제공하는 측
  • 컨슈머 (Required Interface): 호출하는 측, 서비스를 이용하는 측

프로바이더 및 컨슈머를 정리해 인터페이스 또는 서비스 인터페이스라고 부르고, 이런 서비스 인터페이스를 갖는 포트를 서비스 포트라고 부릅니다.

서비스 포트와 인터페이스

서비스 인터페이스와 서비스 포트의 관계에 대해 자세히 설명합니다.

component_port_interface_ko.png
컴포넌트, 포트, 인터페이스

포트란 컴포넌트에 부속되는 컴포넌트 간의 접속의 단점이 되는 부분을 가리킵니다. 컴포넌트 간의 접속은 컴포넌트에 부속되는 포트 사이에서 접속에 관한 조정을 해 어떠한 상호 작용(데이터나 커멘드의 주고 받음)을 실시할 수 있는 상태로 하는 것을 의미합니다.

포트 자체는 데이터나 커멘드의 주고 받음에 대해 어떠한 기능도 제공하지 않습니다. 실제로 커멘드의 주고 받음을 하는 것은 서비스 인터페이스(프로바이더 와 컨슈머)입니다. 일반적으로 포트에는 기능적으로 관련있는 임의의 수의 임의의 방향의 인터페이스를 부가하는 것이 가능합니다. 이것에 의해 커멘드의 주고 받음을 한 방향만이 아닌 쌍방향으로도 가능합니다.

컨슈머와 프로바이더는 포트가 접속되었을 때, 어느 조건에 근거해 접속되어 컨슈머에서 프로바이더의 기능을 호출할 수 있습니다. 컨슈머와 프로바이더를 접속하기 위해서는 양측의 형태가 같거나 호환성이 있어야만 합니다.

같은 형태라는 것은 동일한 인터페이스 정의를 가지는 것이고, 호환성이 있다라는 것은 프로바이더의 인터페이스가 컨슈머 인터페이스의 서브 클래스의 하나(역으로 말하면, 컨슈머의 인터페이스가 프로바이더의 인터페이스의 슈퍼 클래스의 하나)라는 것이 됩니다.

서비스 포트

RT컴포넌트는 데이터 포트와 같이 임의의 수의 서비스 포트를 가질 수 있습니다. 또한, 서비스 포트에는 임의의 종류, 임의의 갯수의 프로바이더 또는 컨슈머를 부가할 수 있습니다.

이하는 OpenRTM-aist의 샘플 컴포넌트 MyServiceProvider에서 발췌한 포트와 프로바이더의 등록을 위한 코드입니다.

 RTC::ReturnCode_t MyServiceProvider::onInitialize()
 {
   // Set service provider to Ports
   m_MyServicePort.registerProvider("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
   
   return RTC::RTC_OK;
 }

m_MyServicePort.registerProvider()로 프로바이더를 서비스 포트 오브젝트 m_MyServicePort에 등록하고 있습니다. 제3인수가 실체 프로바이더 오브젝트 입니다. 다음으로 컴포넌트의 프레임 워크 클래스인 RTObject 클래스의 addPort()함수로 포트를 컴포넌트에 등록하고 있습니다.

위에서와 같이 샘플 컴포넌트 MyServiceConsumer에서 발췌한 코드를 나타냅니다.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   // Set service consumers to Ports
   m_MyServicePort.registerConsumer("myservice0", "MyService", m_myservice0);
   
   // Set CORBA Service Ports
   addPort(m_MyServicePort);
 
   return RTC::RTC_OK;
 }

프로바이더의 경우와 거의 같이 m_MyServicePort.registerConsumer()함수로 컨슈머를 포트에 등록해 그 포트를 addPort()함수로 컴포넌트에 등록하고 있습니다.

이상 특별히 설명도 없이 각각 m_myservice0라고 하는 오브젝트가 프로바이더 또는 컨슈머라고 하며 코드 예를 나타냈습니다만, 아래에 이러한 인터페이스가 어떻게 정의되고 오브젝트가 어떻게 구현되고 있는지를 설명 하겠습니다.

인터페이스 정의

인터페이스란 무엇입니까? C++라면 순수 가상 클래스를 인터페이스라고 부르고, Java는 언어 레벨에서 interface 키워드가 준비되어 있습니다.

OpenRTM-aist에서는 언어나 OS에 의존하지 않고 네트워크 투과라는 특징이 있습니다만 이것은 CORBA라고 불리우는 분산 오브젝트 미들웨어를 이용하는 것에 의해 실현되고 있습니다. CORBA는 국제 표준화 단체 OMG에서 표준화 되어 있는 분산 오브젝트 미들웨어로 표준에 따라서 많은 회사나 단체, 개인 등이 다양한 실장을 제공하고 있습니다.

OpenRTM-aist에서는 인터페이스는 IDL이라고 부르는 CORBA의 인터페이스 정의 언어에 의해 정의합니다. 이 IDL은 언어에 의존하지 않는 인터페이스 정의 방법을 제공하고, 또한 IDL 컴파일러라고 불리는 스텁이나 스켈레톤을 생성 툴을 이용하는 것으로 각종 언어에 대응한 코드가 자동적으로 생성됩니다. 스 텁이란 원격 오브젝트를 호출하기 위한 프록시 오브젝트를 위한 코드이고 스켈레톤은 프로바이더를 구현하기 위한 베이스가 되는 코드입니다.

이렇게 자동 생성된 코드 덕분에 다른 언어끼리의 호출도 무결절적으로 실시할 수 있습니다. 예를 들면 C++로 작성된 프로바이더를 Python이나 Java등으로 용이하게 호출할 수 있는 것 입니다.

아래는 OpenRTM-aist의 샘플에서 사용되고 있는 IDL 정의입니다.

 module SimpleService {
   typedef sequence<string> EchoList;
   typedef sequence<float> ValueList;
   interface MyService
   {
     string echo(in string msg);
     EchoList get_echo_history();
     void set_value(in float value);
     float get_value();
     ValueList get_value_history();
   };
 };

module이란 C++에서 말하는 네임 스페이스와 같은 것으로 이것에 의해 인터페이스 명을 취하여 충돌을 막을 수 있습니다.

C언어 등과 같이 typedef 키워드가 있습니다. 위의 예에서는 sequence라고 불리는 동적 배열형을 정의하고 있습니다. 하나는 string(문자열 형)형의 리스트로서 EchoList형, 또하나는 float형의 리스트로서 ValueList형을 정의하고 있습니다. 특히 sequence형은 typedef하지 않고 정의중에 직접 사용할 수 없기 때문에 이와 같이 미리 typedef해 둘 필요가 있습니다.

다음에 interface로 시작하는 부분이 실제 인터페이스의 정의가 됩니다. MyService 인터페이스에는 5개의 함수(IDL에서는 오퍼레이션이라고 부릅니다.)가 정의되어 있습니다. 대부분은 C언어나 Java 등과 같은 정의입니다만, IDL에서는 인수가 입력인지 출력인지를 명확하게 하기 위해 인수 선언 전에 in, out 또는 inout의 수식자가 붙습니다.

IDL 컴파일

그림에 IDL 정의, IDL 컴파일, 프로바이더의 구현 및 스텁의 이용의 흐름을 나타냅니다.

idlcompile_stub_skel_ko.png
IDL 컴파일과 스텁, 스켈레톤

정의된 IDL을 IDL 컴파일러로 컴파일하면 일반 스텁과 스켈레톤( 또는 서버와 클라이언트라고 부르는 경우도 있음)을 위한 코드가 생성됩니다.

클라이언트, 즉 서비스를 이용하는 측은 스텁 코드를 인클루드 하는 등 스텁에서 정의되고 있는 프록시(대리) 오브젝트를 이용하여 원격에 있는 서버의 기능에 액세스합니다. 아래에 C++에서의 코드 예를 나타냅니다.

 MyService_var mysvobj = <어떤 방법으로 원격 오브젝트의 참조를 취득>
 Retval retval = mysvobj->myoperation(argument);

MyService_var라는 것은 프록시 오브젝트를 위한 선언입니다. mysvobj에 원격 오브젝트의 참조를 어떠한 형태로 대입하면, 그 밑에서 행해지고 있는 myoperation()함수의 호출은 실제로는 원격에 존재하는 오브젝트에 대해 행해집니다. 이 MyService_var 클래스가 정의되어 있는 것이 스텁에 해당합니다.

한편, 위의 방법에 의해 실제로 호출된 서버측의 오브젝트는 아래와 같이 스켈레톤 클래스를 물려받아 아래와 같이 구현됩니다.

 class MyServiceSVC_impl
   : public virtual MyService,
     public virtual PortableServer::RefCountServantBase
 {
 public:
    MyServiceSVC_impl() {};
    virtual ~MyServiceSVC_impl() {};
    Retval myoperation(ArgType argument)
    {
      return do_ something(argument);
    }
 };

더욱이 여기서 정의된 하위 클래스를 인스턴스화 하여 CORBA 오브젝트 로서 액티베이트 하는 것으로 원격에서 오퍼레이션을 호출할 수 있습니다.

 // CORBA의 ORB 등을 기동하기 위한 여러가지 방법
 MyServiceSVC_impl mysvc;
 POA->activate_object(id, mysvc);

IDL을 정의해 컴파일하는 것으로 분산 오브젝트를 정의하여 이용할 때 필요한 대부분의 코드가 자동적으로 생성됩니다. 단, 위의 「어떤 방법으로 원격 오브젝트의 참조를 취득」하거나, 「CORBA의 ORB을 기동하기 위한 여러가지 방법」라고 하는 것은 CORBA를 직접 이용하는 경우에는 여전히 코딩할 필요가 있는 부분이고, 이것들은 CORBA를 이용하는데도 이해하기 어렵거나 번잡한 작업이 필요한 부분입니다.

그렇지만 OpenRTM-aist를 이용하면 이러한 CORBA의 여러가지 호출의 대부분은 은폐되어 작성자는 클라이언트의 호출, 하위 클래스의 구현에만 집중할 수 있습니다. 이하에는 하위 클래스를 작성해 프로바이더로서 컴포넌트에 등록하는 방법, 컨슈머로서 프로바이더를 이용하는 방법에 대해 자세히 설명합니다.

구현

서비스 포트의 구현에 있어서는 RTCBuilder를 사용하는 것이 편리합니다. 스스로 서비스 포트, 프로바이더 및 컨슈머를 구현하는 것도 가능하지만, CORBA나 IDL 컴파일러에 정통해야 하고 Makefile과 코드의 여러 부분을 수정해야 하기 때문에 그다지 권해드리지 않습니다.

RTCBuilder의 자세한 사용법은 RTCBuilder의 매뉴얼을 참조해 주십시오.

IDL 정의

서비스 포트를 이용할 때는 이용하는 인터페이스를 미리 IDL로 정의하던지 기존의 IDL 파일을 적당한 디렉토리에 배치할 필요가 있습니다.

IDL의 정의 방법에 대해서는 여기에서는 자세히는 설명하지 않지만 대체로 아래와 같은 포맷으로 정의할 수 있어, C언어나 Java에 익숙한 사용자라면 비교적 용이하게 정의할 수 있습니다.

 // 네임 스페이스를 위한 모듈을 정의할 수 있다.
 // 모듈 정의는 적극적으로 사용하는 것이 추천 된다.
 module <모듈 명>
 {
   // 구조체를 정의할 수 있다.
   struct MyStruct // 구조체 명
   {
     short x; // int형은 short와 long만 이용 가능
     short y;
     long  a;
     long  b;
     double dval; // 부동 소수점형은 float와 double만 이용 가능
     float fval;
     string strval; // 문자열을 위해 string가 이용 가능
   };
 
   // 동적 배열 sequence 형은 미리 typedef 할 필요가 있다
   typedef sequence<double> DvalueList;
   typedef sequence<MyStruct> MyStructList; // 임의의 구조체도 sequence형으로 가능
 
   // 인터페이스 정의
   interface MyInterface // 인터페이스 명
   {
     void op1(); // 리턴값 없고, 인수 없을 경우
 
     // NG: 대문자・소문자를 구별하지 않는 언어에서는 이하의 정의가 문제가 되기 때문에 IDL에서는 에러가 된다
     // short op2(in MuStruct mystruct);
     short op2(in MyStruct mydata); // 인수는 {in, out, inout} 로 방향을 지정
 
     oneway void op3(); // 리턴값 없는 오퍼레이션은 onway 키워드가 이용 가능
 
     void op4(in short inshort, out short outshort, inout short ioshort);
 
     void op5(MyInterface myif); // MyInterface 자신을 인수로 이용하는 것도 가능
   };
 
   // 동일한 IDL 파일에 복수의 interface를 지정하는 것도 가능
   interface YourInterface
   {
     void op1();
   };
 };

RTCBuilder에 의한 설계

위와 같이 IDL에서 정의한 인터페이스를 앞으로 개발하는 RT컴포넌트의 서비스 포트의 프로바이더 혹은 컨슈머로서 사용하기 위해서는 컴포넌트의 코드 제네레이터인 RTCBuilder에서 서비스 포트를 설계해 그 때에 이 IDL정의를 줄 필요가 있습니다.

RTCBuilder의 신규 프로젝트를 작성해 퍼스펙티브를 엽니다. 각종 프로파일(컴포넌트의 이름이나 카테고리 명)등 필요한 설정을 한 후, 서비스 포트 탭을 열면 다음과 같은 화면이 나타납니다.

rtcbuilder_serviceport_tab1_ko.png
서비스 포트 설계 탭

우선, Add Port버튼을 눌러 서비스 포트를 하나 추가합니다. 그렇게 하면 sv_name이라는 서비스 포트가 하나 추가되어 아래의 BuildView의 컴포넌트 박스에 작은 정사각형의 포트가 한개 추가됩니다. RTCBuilder의 에디터 왼쪽 포트 리스트의 sv_name을 클릭하면 우측에 RT-Component Service Port Profile가 표시되고 포트명을 적당한 이름(여기에서는 MyServiceProviderPort)으로 변경합니다.

rtcbuilder_serviceport_tab2_ko.png
서비스 포트의 추가

에디터 왼쪽의 포트 리스트의 MyServiceProviderPort을 클릭해 Add Interface버튼을 클릭하면 MyServiceProviderPort 에 인터페이스 if_name이 하나 추가되고 방금전과 같이 에디터 왼쪽의 if_name을 클릭해 RT-Component Service Port Interface Profile상에서 if_name을 적당한 이름(여기에서는 MyServiceProvider)로 변경 합니다. 아래의 BuildView에서는 정사각형의 포트에 롤리팝이 추가되어 프로바이더 (Provided Interface)가 포트에 부가되는 것을 시각적으로 확인할 수 있습니다.

rtcbuilder_serviceport_tab3_ko.png
서비스 인터페이스(프로바이더)의 추가

에디터 우측의 Interface Profile에서는 인터페이스의 프로파일을 설정합니다. 예를 들면 방향의 드롭 다운 리스트에서는 대상의 인터페이스가 프로바이더(Provided)인지 컨슈머(Required)인지를 지정합니다.

rtcbuilder_direction_ddown_ko.png
서비스 인터페이스의 「방향」의 설정

여기에서는 프로바이더를 추가하려고 하기 때문에 Provided 대로 합니다. 이 외, 인스턴스명, 변수명 등도 지정할 수 있습니다만, 필수는 아닙니다. 인터페이스명은 접속시에 프로바이더와 컨슈머의 인스턴스명이 같다면 대응 관계를 지정하지 않더라도 포트의 접속을 자동적으로 실시하는 경우에 이용됩니다.

serviceif_autoconnection_ko.png
서비스 인터페이스명의 인스턴스명과 자동접속

다만, 인스턴스명이 차이가 나도 접속시에 임의의 인터페이스 끼리를 접속할 수 없기 때문에 입력은 필수는 아닙니다. 또 변수명은 코드를 생성했을 때에 프로바이더 오브젝트를 대입하는 변수명을 지정하기 위한 항목입니다만, 이것도 인터페이스명에서 자동적으로 생성되기 때문에 입력은 임의입니다.

다음에 IDL의 지정과 인터페이스형의 지정을 합니다. 위에서 정의한 것과 같은 IDL을 적당한 디렉토리에 배치하고, IDL파일 지정 박스 옆의 Browse버튼을 눌러 대상이 되는 IDL을 지정합니다. 그러면 지정된 IDL로 정의된 인터페이스가 그 아래의 인터페이스형의 드롭 다운 리스트에 나타납니다. 이 드롭 다운 리스트에서 이 포트에 추가한 인터페이스명을 선택합니다. IDL파일에 문법 에러 등이 있을 경우에는 드롭 다운 리스트에 희망하는 인터페이스가 나타나지 않습니다. 다시 IDL파일의 정의를 클릭해 주십시오.

rtcbuilder_interfacetype_ddwon_ko.png
인터페이스형의 선택

또한, 위 설명의 방향 드롭 다운 리스트에서 Required를 지정하면 이 인터페이스는 컨슈머가 됩니다. 이하는 다른 컴포넌트 MyServiceConsumer의 서비스 포트와 인터페이스 설정 화면의 예입니다.

rtcbuilder_serviceport_tab4_ko.png
서비스 인터페이스(컨슈머)의 추가

에디터 아래의 BuildView에 포트에 소켓이 추가되어 컨슈머 (Required interface)가 포트에 부가되는 것을 확인할 수 있습니다.

프로바이더의 구현

프로바이더라는 것은 문자 그대로 서비스를 프로바이드(제공)하기 위한 인터페이스 입니다. 따라서, IDL로 정의된 인터페이스를 갖는 서비스의 내용을 구현할 필요가 있습니다.

프로바이터 인터페이스를 갖는 컴포넌트를 RTCBuilder로 설계한 경우 코드 생성을 하면, 컴포넌트의 소스의 모형과 함께, 예를 들어 C++의 경우는 <서비스 인터페이스명>SVC_impl.cpp과 <서비스 인터페이스명>SVC_impl.h이라는 프로바이더의 구현 코드의 모형도 생성됩니다.

rtcbuilder_svcimpl_cxxsrc_ja.png
서비스 프로바이더 구현 파일 (C++,Python,Java)

아래에 각 언어에서 생성되는 프로바이더의 구현 모형 코드의 파일명을 나타냅니다.

생성되는 프로바이더의 모형 코드 파일
C++ <interface name>SVC_impl.cpp
<interface name>SVC_impl.h
Python <interface name>_idl_example.py
Java <interface name>SVC_impl.java

rtcbuilder_svcimpl_pysrc_ja.png
서비스 프로바이더 구현 파일 (Python)

rtcbuilder_svcimpl_javasrc_ja.png
서비스 프로바이더 구현 파일 (Java)

이러한 구현 모형에는 IDL에 정의된 인터페이스에 상당하는 클래스가 미리 정의되어 있습니다.

여기에서는 C++에서의 구현 방법을 예를 들어 IDL로 정의된 오퍼레이션의 몇개인가를 구현해 볼 것입니다.

echo()함수의 작성

먼저, echo() 멤버 함수를 보겠습니다.

 /*
  * Methods corresponding to IDL attributes and operations
  */
 char* MyServiceSVC_impl::echo(const char* msg)
 {
   // Please insert your code here and remove the following warning pragma
 #ifndef WIN32
   #warning "Code missing in function <char* MyServiceSVC_impl::echo(const char* msg)>"
 #endif
   return 0;
 }

#warning 프리프로세서 지시문이 있습니다만, 이것은 gcc로 컴파일 할 때에 이 함수가 구현되어 있지 않은 것을 경고하기 위한 것이므로, #ifndef마다 삭제합니다.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

또한 이 함수는 echo() 함수의 인수에 주어진 문자열을 단지 호출한 측에 돌려주는 것만의 기능을 제공한다고 합니다. 따라서, 이하와 같이 구현하면 좋을 것입니다.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return msg;
 }

그러나 이것은 컴파일시에 에러가 됩니다. const char*을 char*에 건네주고 있기 때문입니다. 또한, CORBA의 오브젝트의 구현 방법으로서도 잘못되어 있습니다. CORBA에서 return으로 반환되는 오브젝트는 ORB(Object Request Broker, 원격 오브젝트의 호울 부분을 맡는 부분, CORBA의 코어)에 의해 해체된다고 하는 룰이 있기 때문입니다. (return시에는 오브젝트의 소유권을 포기한다라고도 말합니다.)

따라서 return에는 별도 영역을 확보해 msg의 내용을 복사한 문자열을 반환할 필요가 있습니다. 이것에 따르면, 이하와 같이 구현한다면 좋을 것입니다.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   char* msgcopy;
   msgcopy = malloc(strlen(msg));
   strncpy(msgcopy, msg, strlen(msg));
   return msgcopy;
 }

여기에서는 malloc으로 영역을 확보하고 있습니다만 ORB는 free로 영역을 해체하는지 delete로 해체하는지는 모릅니다. 실제로 CORBA에서는 오브젝트(구조체나 배열 또는 그 복합형 등도 포함)나 문자열을 취급하기 위한 방법이 별도로 정해져 있어, 거기에 따라 함수의 인수를 받거나, 돌려주거나 할 필요가 있습니다.

CORBA에서 정해진 방법에 따르면 echo()함수는 이하와 같이 구현할 필요가 있습니다.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   CORBA::String_var msgcopy = CORBA::string_dup(msg);
   return msgcopy._retn();
 }

함수내의 첫번째 행은 CORBA의 문자열 클래스 CORBA::String의 스마트 포인터인 CORBA::String_var형을 선언하고 있습니다. String_var형은 이른바 소유권을 관리하기 위한 스마트 포인터로 STL의 auto_ptr과 비슷합니다.

 CORBA::String_var msgcopy = CORBA::string_dup(msg);

이 String_var형의 변수 msgcopy에 인수의 msg에 저장되어 있는 문자열을 복사하고 있는 것은 CORBA::string_dup()함수입니다. 이 함수에서는 인수로 주어진 문자열을 저장하는데 충분한 메모리 영역을 확보해 그 영역에 인수의 문자열을 복사하고 있습니다.

다음의 행에서는 return으로 호출된 곳으로 msgcopy내의 문자열을 반환하면서 오브젝트의 소유권을 포기, return측에 소유권을 이양하고 있습니다. 아래의 그림에 표시된 것과 같이 ORB에서는 return으로 반환된 문자열을 네트워크상의 원래 호출된 곳으로 송신한 다음, 문자열 오브젝트를 해방합니다.

serviceport_orb_and_provider_ko.png
ORB와 오퍼레이션 호출, 메모리 관리의 관계

이 룰을 잘 이해하면, msgcopy 오브젝트가 echo() 함수내에서 사용되지 않는 것에서 부터, echo()함수의 구현은 최종적으로는 이하와 같이 쓸 수도 있습니다.

 char* MyServiceSVC_impl::echo(const char* msg)
 {
   return CORBA::string_dup(msg);
 }

CORBA::string_dup() 함수에서 문자열 영역의 확보와 내용의 복사를 한 상태에서, 그 소유권을 직접 호출된 곳으로 주는 것이 됩니다.

이와 같이 서비스 프로바이더는 CORBA의 오브젝트이기 때문에 그 구현 방법은 통상의 C++의 구현과는 조금 다른 방식으로 할 필요가 있습니다. 특히, 함수의 인수 및 리턴값의 주고 받는 규약은 조금 복잡하게 보입니다. 다만, 위와 같이 오브젝트의 소유권이라는 사고를 염두에 두고 생각해보면 인수를 어떻게 주고 받아야 할 것인가 또는 리턴값을 어떻게 리턴해야 할 것인가가 저절로 밝혀집니다. 상세한 것에 대해서는 Appendix나 그 외의 CORBA의 참고서 등을 참고해 주십시오.

set_value(), get_value() 와 get_value_history()

다음은 set_value() 함수, set_value() 함수 및 set_value_list() 함수를 동시에 작성해 보겠습니다. 이러한 함수는 set_value()로 설정된 float형의 값을 저장해 두고 get_value()로 그 값을 리턴한다는 단순한 것입니다. 또한, get_value_history()에서는 지금까지 세트된 값의 이력을 저장해 두어, 이력을 리스트로서 리턴한다는 것입니다.

먼저 값을 저장해 놓을 변수를 준비합니다. 현재의 값은 MyServiceSVC_impl 클래스에 CORBA::Float형의 private 멤버로서 준비합니다. 한편, get_value_history()에서는 리턴값에 SimpleService::ValueList라는 CORBA의 시퀀스형이 사용되고 있기 때문에 이것을 멤버 변수로서 갖도록 합니다. 이러한 변수 선언을 MyServiceSVN_impl.h의 MyServiceSVC_impl 클래스 정의의 마지막에 다음과 같이 추가합니다.

 class MyServiceSVC_impl
   : public virtual POA_SimpleService::MyService,
    public virtual PortableServer::RefCountServantBase
 {
   : (중략)
 private:
   CORBA::Float m_value; // 이 행을 추가한다
   SimpleService::ValueList m_valueList; // 이 행을 추가한다
   };

변수의 초기화도 잊지말고 합니다. MyServiceSVC_impl.cpp의 생성자에서 m_value는 0.0으로, m_valueList는 길이 0으로 초기화 합니다.

 MyServiceSVC_impl::MyServiceSVC_impl()
 : m_value(0.0), m_valueList(0)
 {
   // Please add extra constructor code here.
 }

다음 set_value()함수를 작성합니다. 인수로 주어진 값을 멤버 변수 m_value에 대입하는 것과 동시에 m_valueList에도 추가합니다. CORBA의 시퀀스형은 동적 배열형으로 []오퍼레이터와 함께 length(), length(CORBA::ULong)의 함수를 이용할 수 있습니다. length()함수는 현재 배열의 길이를 반환해, length(CORBA::ULong)함수는 현재의 배열의 길이를 설정 합니다. 구현은 아래와 같이 됩니다.

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 현재값
 
   CORBA::ULong len(m_valueList.length()); // 배열의 길이를 취득
   m_valueList.length(len + 1); // 배열의 길이를 1개 늘림
   m_valueList[len] = value; // 배열의 마지막에 value를 추가
 
   return;
 }

echo()함수와는 다르게 CORBA::Long형은 C++의 long int와 등가로, 오브젝트 소유권, 영역 확보나 폐기 등은 생각할 필요가 없습니다. 따라서 위와 같이 단순한 대입으로 상관없습니다. 또, 배열형은 2종류의 length()함수와 [] 오퍼레이터를 사용해 배열의 길이를 1개 증가하고 마지막에 인수 값을 대입하고 있습니다. 또한 OpenRTM-aist에서는 CORBA의 시퀀스형을 STL의 vector에 가까운 형태로 이용하기 때문에 함수 템플렛을 제공하고 있어, 그것을 사용하면

 void MyServiceSVC_impl::set_value(CORBA::Float value)
   throw (CORBA::SystemException)
 {
   m_value = value; // 현재값
   CORBA_SeqUitl::push_back(m_valueList, value);
 
   return;
 }

와 같이 쓰는 것이 가능합니다. CORBA_SeqUtil.h에서는 for_each(), find(), push_back(), insert(), front(), back(), erase(), clear()라는 함수가 정의되어 있습니다.

get_value()는 아래와 같이 됩니다.

 CORBA::Float MyServiceSVC_impl::get_value()
   throw (CORBA::SystemException)
 {
   return m_value;
 }

저정된 값을 return으로 호출한 곳으로 리턴할 뿐입니다. 여기에서도 조금 전의 echo()의 예와는 다르게 CORBA::Float이 프리미티브형이기 때문에 소유권 등을 고려할 필요는 없습니다.

마지막으로 set_value_history()의 구현을 보고 갑니다. 값의 이력이 저장된 m_valueList를 리턴한다면 좋을 것 이라고 생각되지만 방금 전 설명한 소유권과 영역의 해방의 문제가 있기 때문에 아래와 같이 구현할 필요가 있습니다.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   SimpleService::ValueList_var vl;
   vl = new SimpleService::ValueList(m_valueList);
   return vl._retn();
 }

함수내 첫번째 행에서는 시퀀스형 오브젝트를 위한 스마트 포인터인 SimpleService::valueList_var형의 변수명을 선언하고 있습니다. 또한 다음의 행에서는 이 스마트 포인터에 대해 copy 생성자를 호출해 그 포인터를 대입하고 있습니다. 이것에 의해 영역의 확보와 값의 복사가 동시에 행해집니다. 마지막에 vl.retn()으로 vl이 저장하고 있는 시퀀스형의 오브젝트의 소유권을 포기하고 return측에 오브젝트를 건네주고 있습니다.

그리고 vl은 함수내에서 사용되지 않기 때문에 이하와 같이 쓸 수도 있습니다.

 SimpleService::ValueList* MyServiceSVC_impl::get_value_history()
   throw (CORBA::SystemException)
 {
   return new SimpleService::ValueList(m_valueList);
 }

이상 프로바이더의 작성에 대해 설명하였습니다만, 프로바이더가 이른바 CORBA 오브젝트이므로 사용하는 형, 변수의 주고 받는 구조 등 CORBA의 룰에 따라 작성해야 합니다. 처음에는 번거롭게 느껴지겠지만 프리미티브형에 대해서는 종래대로의 작성, 복잡한 오브젝트에 대해서는 메모리의 확보와 해제가 어디서 행해지는지, 소유권은 어느 쪽에 있는지를 이해하면 어떻게 작성해야 하는 것인가 이해할 수 있다고 생각합니다.

컨슈머의 이용

컨슈머에서는 위에서 작성된 서비스 프로바이터를 호출해 그 기능을 사용하는 것이 됩니다. 컨슈머를 갖는 컴포넌트의 모형 코드를 RTCBuilder로 생성한 경우에는 프로바이더의 경우와는 다르게 특별한 파일이 생성되지 않습니다.

그 대신 컴포넌트의 헤더에 아래와 같은 프로바이더의 플레이스 홀더인 컨슈머 오브젝트가 선언됩니다.

      : (중략)
   // Consumer declaration
   // <rtc-template block="consumer_declare">
   /*!
    */
   RTC::CorbaConsumer<SimpleService::MyService> m_MyServiceConsumer;
 
   // </rtc-template>
 
  private:
      : (중략)

이것은 RTC::CorbaConsumer 클래스 템플릿에 형인수 SimpleService::MyService를 주고 MyService형의 컨슈머를 선언하고 있는 것이 됩니다. 또한 작성 파일 쪽은 onInitialize()함수에서 컨슈머의 포트에 등록과 포트의 컴포넌트에 등록이 행해지고 있는 것을 확인할 수 있습니다.

 RTC::ReturnCode_t MyServiceConsumer::onInitialize()
 {
   : (중략)   
  // Set service consumers to Ports
   m_MyServiceConsumerPortPort.registerConsumer("MyServiceConsumer",
                                                "SimpleService::MyService",
                                                m_MyServiceConsumer);
  
   // Set CORBA Service Ports
   addPort(m_MyServiceConsumerPortPort);
   // </rtc-template>
 
   return RTC::RTC_OK;
 }
 

헤더에서 선언되고 있는 m_MyServiceConsumer 변수가 registerConsumer() 멤버 변수에 의해 포트에 등록되어 있는 것을 알 수 있습니다. 제1 인수에는 이 컨슈머의 「인스턴스 변수」가, 제2 인수에는 컨슈머의 「인터페이스형」이 그리고 제3 인수에는 컨슈머의 인스턴스인 m_MyServiceConsumer 변수가 각각 주어지고 있습니다. 이것에 의해 컨슈머가 인터페이스명, 형이름 모두 포트에 관련되고 있는 것이 됩니다.

컨슈머 m_MyServiceConsumer는 위에서 설명한 것과 같이 프로바이더의 플레이스 홀더가 되어 있습니다. C++에서는 오브젝트에의 포인터와 같이 취급할 수 있습니다.

MyService 인터페이스에서는 string형(CORBA의 string형) 인수를 하나 취하는 echo() 오퍼레이션이 정의되어 있습니다. 따라서 예를들면 이하와 같이 echo()함수를 호출할 수 있습니다.

 m_MyServiceConsumer->echo("Hello World");

C++에서는 위와 같이 포인터, Java나 Python에서는 참조와 같은 오퍼레이션을 호출할 수 있는 것입니다.

그럼 여기에서 눈치가 빠른 사람이라면 포인터 또는 참조가 가르키는 곳은 도대체 어떻게 되어 있는지 생각할 것입니다. C++ 등에서도 예를 들어 아래와 같은 코드는 segmentation fault에러가 발생합니다.

 class A {
 public:
   const char* echo(const char* msg) {
     std::cout << msg << std::endl;
     return msg;
   }
 };
 
 int main (void) {
   A* a;
   a->echo("Hello World!!");
 }

a는 null 포인터이기 때문에 아무것도 오브젝트를 가르키지 않습니다. 이것과 같이 위의 m_MyServiceConsumer도 컴포넌트 기동 직후에는 어떠한 오브젝트도 가리키지 않기 때문에 당연히 오퍼레이션을 호출할 수 없습니다. 위의 class A의 경우에서는

 int main (void) {
   A* a;
   a = new A();
   a->echo("Hello World!!");
 }

오브젝트를 new로 생성해 변수 a에 대입하면 a는 그 시점에서 어느 오브젝트를 가리키는 정당한 포인터이기 때문에, class A의 멤버 함수인 echo()를 호출할 수 있습니다.

그렇지만 컴포넌트 없는 컨슈머가 호출하고 싶은 것은 네트워크상의 어디엔가에 있는 오브젝트의 오퍼레이션입니다. 따라서, m_MyServiceConsumer가 가리키는 것은 원격 오브젝트의 참조(CORBA 오브젝트 참조(레퍼런스))입니다.

실제로 아래의 그림과 같이 컨슈머는 그 포트가 프로바이더를 갖는 포트와 접속될 때, 대응하는 오브젝트 참조를 받습니다. 접속에 의해 컨슈머는 정당한 오브젝트를 가리키는 것이 되고 이렇게 하여 처음으로 오퍼레이션을 호출할 수 있습니다.

serviceport_connection_and_reference_ko.png
서비스 포트의 접속과 오브젝트(참조) 레퍼런스

접속 후는(상대의 포트에 적당한 프로바이더가 존재한다면) 컨슈머의 오퍼레이션을 호출할 수 있습니다만, 접속하고 있지 않은 경우 또는 유호한 참조가 세트되어 있지 않은 경우에는 컨슈머 오브젝트는 예외를 던집니다. 그래서 컨슈머를 이용하는 경우 언제 접속이 되었는지, 또한 언제 접속이 끊어졌는지 모르기 때문에 항상 이 예외를 포착해 적절히 처리할 필요가 있습니다.

 try
 {
   m_MyServiceConsumer->echo("Hello World!!");
 }
 catch (CORBA::SystemException &e)
 {
   // 예외시의 처리
      std::cout << "포트가 접속되어 있지 않습니다."" << std::endl;
 }
 catch (...)
 {
   // 그외의 예외
 }

또한, onExecute()멤버 함수내에서 예외가 발생해 함수 내부에서 포착되지 않은 경우, RTC는 에러 상태로 천이합니다.

이상을 근거로 하여 MyserviceConsumer 컴포넌트를 작성합니다. 이 예는 onExecute()에서 유저로 부터 입력을 대기하고, 각 오퍼레이션에 대응한 커멘드를 받아 커멘드에 따라 원격의 프로바이더의 오퍼레이션을 호출하여 결과를 돌려주는 간단한 것입니다.

그럼 먼저 유저에게 사용할 수 있는 커멘드를 게시하는 부분부터 봅시다.

RTC::ReturnCode_t MyServiceConsumer::onExecute(RTC::UniqueId ec_id) {

  try
    {
      std::cout << std::endl;
      std::cout << "Command list: " << std::endl;
      std::cout << " echo [msg]       : echo message." << std::endl;
      std::cout << " set_value [value]: set value." << std::endl;
      std::cout << " get_value        : get current value." << std::endl;
      std::cout << " get_echo_history : get input messsage history." << std::endl;
      std::cout << " get_value_history: get input value history." << std::endl;
      std::cout << "> ";
      
      std::string args;
      std::string::size_type pos;
      std::vector<std::string> argv;
      std::getline(std::cin, args);

먼저 위에서 설명한 대로 컨슈머가 발생하는 예외를 포착하기 위해서는 try절로 둘러쌉니다. 사용가능한 커멘드 리스트를 표시해, 유저의 입력을 getline()함수로 받고 있습니다.

      
      pos = args.find_first_of(" ");
      if (pos != std::string::npos)
        {
          argv.push_back(args.substr(0, pos));
          argv.push_back(args.substr(++pos));
        }
      else
        {
          argv.push_back(args);
        }

이러한 커멘드 중에 인수를 받는 것은 echo와 set value만으로, 한편 이러한 커멘드는 인수를 한개만 받습니다. 받은 문자열을 최초의 공백에서 분할해 argv[0] = 커멘드, argv[1] = 인수로서 string의 vector에 저장합니다. echo, set_value커멘드에서는 argv[1]를 인수로서 이용하여 다른 커멘드에서는 단순히 무시하기로 하겠습니다.

        
      if (argv[0] == "echo" && argv.size() > 1)
        {
          CORBA::String_var retmsg;
          retmsg = m_myservice0->echo(argv[1].c_str());
          std::cout << "echo return: " << retmsg << std::endl;
          return RTC::RTC_OK;
        }

echo 커멘드의 작성입니다. argv[0]가 echo인 경우, argv[1]을 인수로 해 echo()함수를 호출합니다. echo()의 CORBA의 string형의 리턴값을 받기 위한 변수로서, retmsg를 선언하고 있습니다. echo()의 리턴값의 소유권은 이쪽에 있기 때문에, 받은 후에 적절히 영역을 해제할 필요가 있습니다만, String_var형의 스마트 포인터를 이용하면 불필요하게 되는 시점에 적절히 영역을 해제하여 줍니다. 리턴값을 표시하고 return RTC::RTC_OK로 onExecute()함수를 빠져 나옵니다.

        
      if (argv[0] == "set_value" && argv.size() > 1)
        {
          CORBA::Float val(atof(argv[1].c_str()));
          m_myservice0->set_value(val);
          std::cout << "Set remote value: " << val << std::endl;
          return RTC::RTC_OK;
        }

set_value 커멘드의 작성입니다. 인수 argv[1]의 문자열을 CORBA::Float형으로 변환해 set_value() 오퍼레이션의 인수에 주고 있습니다.

        
      if (argv[0] == "get_value")
        {
          std::cout << "Current remote value: "
                    << m_myservice0->get_value() << std::endl;
          return RTC::RTC_OK;
        }

get_value 커멘드는 set_value 커멘드에서 설정한 값을 취득합니다. get_value() 오퍼레이션은 리턴값이 CORBA::Float로 값을 건네주기 위해 오브젝트의 소유권등은 특별히 생각하지 않아도 괜찮습니다. 여기에서는 리턴값을 그대로 std::cout으로 콘솔에 표시시키고 있습니다.

      if (argv[0] == "get_echo_history")
        {
          EchoList_var elist = m_myservice0->get_echo_history();
          for (CORBA::ULong i(0), len(elist.length(); i <len; ++i)
          {
            std::cout << elist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

get_echo_history 커멘드에서는 get_echo_history()의 결과를 받아, 그것까지 echo 커멘드로 인수로 주어진 문자열의 리스트를 돌려주고 있습니다. get_echo_history()함수의 리턴값은 CORBA의 시퀸스형인 EchoList입니다. 시퀸스형에 대해서도 스마트 포인터인 _var형이 정의되어 있기 때문에, 이것을 이용합니다. 배열의 길이를 취득하기 위한 length() 함수를 이용할 수 있기 때문에, 길이를 조사해 for문에서 모든 요소를 표시하고 있습니다. 시퀸스형의 _var형에서는 위와 같이 [] 오퍼레이터를 이용해 C언어의 배열과 같이 각 요소에 액세스할 수 있습니다.

      if (argv[0] == "get_value_history")
        {
          ValueList_var vlist = m_myservice0->get_value_history();
          for (CORBA::ULong i(0), len(vlist.length()); i < len; ++i)
          {
            std::cout << vlist[i] << std::endl;
          }
          return RTC::RTC_OK;
        }

마지막으로 get_value_history 커멘드입니다. get_value_history() 오퍼레이션을 호출하여, 지금까지 설정된 최근 몇개의 리스트를 표시합니다. get_value_history()함수의 리턴값은 CORBA::Float의 시퀸스형인 ValueList입니다. 요소는 CORBA::Float이기 때문에 오브젝트의 소유권등은 고려하지 않아도 되지만, 시퀸스형은 그것 자신이 오브젝트이므로 소유권을 고려해야 하기 때문에 여기에서는 _var형의 변수로 받고 있습니다.

 std::cout << "Invalid command or argument(s)." << std::endl;
     }
   catch (CORBA::SystemException &e)
     {
       std::cout << "No service connected." << std::endl;
     }
   return RTC::RTC_OK;
 }

마지막으로 위의 어느 것에도 맞지 않았던 커멘드의 경우에, 메세지를 보내고 있습니다. 또, 컨슈머에 참조가 세트되어 있지 않은 경우를 포함한 예외를 포착하기 위한 catch절이 있습니다.

이상으로 컨슈머에 의한 오퍼레이션의 호출법에 대해 간단히 예를 들어 설명하였습니다. 컨슈머를 이용할 때는 반드시 오브젝트 참조가 세트되었다고는 한정할 수 없으므로, 반드시 예외를 포착해 대처하는 것과 각 오퍼레이션의 호출이 CORBA의 룰에 근거하여 행해지는 것을 유의하여 주십시오.

컨피그레이션 (기초편)

컨피그레이션이란

로보트 시스템을 구축하데 시스템의 외부환경, 사용 상황이나 개별 디바이스, 로봇의 특성에 따라 작성하는 프로그램 내의 파라메터를 변경하는 일이 종종 있습니다. 단순한 실험을 하기 위한 간단한 프로그램에서는 파라메터를 하드 코드하고 변경할 때마다 직접 고쳐 써, 컴파일하는 것으로 대처할 수 있을지도 모릅니다. 좀 더 진행하여 파라메터를 파일 등에서 읽거나 커멘드 라인 인수로 건네는 등의 궁리를 하는 것으로 재이용성은 훨씬 높아집니다. 하나의 프로그램을 용도에 따라 재이용하기 위해서는 이러한 파라메터를 내장시키지 말고 외부화하는 것이 매우 중요하게 됩니다.

RT컴포넌트에 의해 구축된 RT시스템에서는 다양한 사람이 작성한 다양한 컴포넌트가 협조하여 동작하기 때문에, 코어 로직 내부에서 사용되는 파라메터를 유저가 자유롭게 정의하여 실행시에 외부에서 변경하기 위한 기능이 준비되어 있습니다. 이것을 컨피그레이션(기능)이라고 부릅니다. 컨피그레이션은 복수의 파라메터 세트를 갖을 수 있고, 파라메터 세트를 일제히 바꿔 넣을 수도 있습니다. 파라메터를 미리 변경가능하게 해두는 것으로 RTC를 다양한 시스템에서 간단히 재이용할 수 있습니다.

configuration_example_ko.png
컨피그레이션의 예

이 섹션에서는 RT컴포넌트의 중요한 기능의 하나인 컨피그레이션에 대해 구조와 실제 사용법에 대해 설명합니다.

컨피그레이션의 구조

아래의 그림은 컨피그레이션의 대략의 구조를 나타내고 있습니다.

configuration_functionality_ko.png
컨피그레이션의 구조

파라메터의 이름의 페어를 컨피그레이션 파라메터 라고 부릅니다. 하나의 컴포넌트는 복수의 컨피그레이션 파라메터를 정의할 수 있고, 그 집합을 컨피그레이션 세트라고 부릅니다.

더욱이 하나의 컴포넌트는 복수의 컨피그레이션 세트를 가질수 있고 그 중 하나의 컨피그레이션만이 실제의 파라메터 값이 됩니다. 이 컨피그레이션 세트를 액티브 컨피그레이션 이라고 부릅니다. 컨피그레이션 세트는 이름을 붙일 수 있어 그 이름에 의해 구별됩니다.

외부 툴(RTSystemEditor나 rtshell 등)을 사용해서 각각의 파라메터 혹은 액티브하게 컨피그레이션 세트를 변경할 수 있습니다. 컨피그레이션의 내용은 컨피그레이션에 연결되어 있는 변수(파라메터 변수) 에 반영되어, RT컴포넌트 내의 로직에서 사용할 수 있습니다. 이렇게 로직 내부에서 사용되는 파라메터를 외부에서 용이하게 변경할 수 있도록 하는 것으로 컴포넌트의 재사용성을 높일 수 있습니다.

  • 컨피그레이션: 컴포넌트 내의 파라메터를 외부화하기 위한 RTC의 기능
  • 컨피그레이션 파라메터: 실제로 컴포넌트 내의 외부화 된 파라메터. 키와 값으로 구성된다.
  • 컨피그레이션 세트: 키와 값의 리스트로 구성되는 파라메터의 리스트. RTC는 복수의 세트를 가질 수 있다.
  • 컨피그레이션 세트명: 컨피그레이션 세트에 붙여진 이름. 세트는 각각 이름으로 구별된다.
  • 액티브 컨피그레이션: RTC는 복수의 컨피그레이션 세트를 가질 수 있고 그 중 실제로 파라메터에 반영되는 유효한 세트를 액티브 컨피그레이션이라고 부른다.
  • 파라메터 변수: 컨피그레이션 파라메터에 연결되어진 변수. 컨피그레이션의 내용이 변경되면 변수에 대입되어 있는 값이 변경된다.

변수 타입이 있는 언어에서는 컨피그레이션 파라메터는 그 언어에서 이용가능 하다면 어떤 타입이라도 사용할 수 있습니다. 물론 타입이 없는 언어에서도 같지만, 중요한 점은 이러한 파라메터를 외주에서 설정할 때 그 값은 문자열에 의해 주어지는 것입니다.

컨피그레이션은 문자열을 각각의 파라메터형으로 변환해 실제의 변수에 세트합니다. 구조체나 배열 등 문자열에서 데이터에 간단하게 변환 불가능한 데이터 타입이라도 변환 함수를 정의하는 것으로 어떤 타입의 데이터라도 똑같이 취급할 수 있습니다. 이것은 미리 IDL정의가 필요한 데이터 포트나 서비스 포트와는 크게 다른 점입니다.

파라메터를 정의

RT컴포넌트 내에서 사용하는 파라메터를 정의하는 방법에는 몇가지가 있습니다.

  • RTCBuilder에서 컴포넌트 설계시에 정의
  • rt-template에서 컨피그레이션 파라메터를 정의
  • 수동으로 필요한 코드를 작성

이하에서 각각의 방법에 대해 설명합니다.

RTCBuilder에 의한 정의

컨피그레이션 파라메터를 정의하는 가장 간단한 방법은 RTC의 설계 툴인 RTCBuilder로 RTC설계시에 컨피그레이션 파라메터를 정의하는 것입니다.

아래의 그림은 RTCBuilder의 컨피그레이션 정의 화면입니다. 이 화면에서 필요한 파라메터를 정의하는 것으로 컨피그레이션 파라메터를 이용하기 위해 필요한 코드가 언어를 불문하고 자동적으로 생성됩니다.

configuration_rtcb00_ko.png
RTCBuilder의 설정 화면

컨피그레이션 파라메터를 이용하기 위해서는 RTCBuilder의 컨피그레이션 탭을 누르고 파라메터 리스트 옆의 Add버튼을 누릅니다. 그러면 컨피그레이션 파라메터가 한개 추가되므로 적당한

  • Name (필수)
  • Type (필수)
  • Default Value (필수)

를 입력합니다.

Name은(디폴트로는 conf_name0 등으로 되어 있으므로) 그 파라메터의 성질 을 단적으로 나타내는 알기 쉬운 이름을 붙여 주십시오. 드롭 다운 리스트에서 선택할 수 있는 데이터 타입은 각 언어에 대해 적절히 변환되어 정의 됩니다. Python 등 명시적으로 형선언이 필요하지 않은 언어에서는 여기에 설정된 형은 코드 상에 나타나지 않을지도 모릅니다.

위에서도 서술한 것처럼 컨피그레이션 파라메터는 값을 문자열로서 주고 문자열을 특정 데이터 타입으로 변환하는 것으로 다양한 형의 파라메터에 대응 가능 합니다. 단, 외부에서 문자열로서 값이 업력되기 때문에 변환 불가능한 문자열 등 부정한 파라메터 입력이 있을 경우, 변환시 에러가 발생하는 경우도 있습니다. 여기서 설정된 디폴트 값은 설정된 값의 변환이 부정한 경우에 대신 사용되는 값입니다.

이외에도 필수가 아닌 항목으로는 이하의 항목이 있습니다. 필요에 따라 입력해 주십시오.

  • Variable name: 변수이름으로 사용하는 문자열. 빈 경우는 위에 Name에 입력한 내용이 사용됩니다.
  • Unit: 이 파라메터의 단위. 현재로서는 사람이 읽는 이외로는 사용되고 있지 않습니다.
  • Constraint: 이 파라메터의 제약 조건을 부여합니다. 이 조건은 RTSystemEditor에서 사용됩니다. 연속적인 값의 경우는 부등호, 열거값의 경우는 ,(콤마)로 구분 등 지정할 수 있습니다.
  • Widget: RTSystemEditor에서 파라메터를 조작할때 사용되는 컨트롤. text, slider, spin, radio중 선택할 수 있습니다.
  • Step: 위의 Widget이 slider 나 spin인 경우 스텝을 지정합니다.

자세한 내용은 화면 우측의 힌트나 RTCBuilder의 메뉴얼을 참조해 주십시오.

rtc-template에 의한 정의

rtc-template는 커맨드 라인에서 사용하는 컴포넌트 템플릿 제네레이터입니다. rtc-template에서 컨피그레이션을 사용하려면 이하와 같이 지정합니다.

    /usr/local/bin/rtc-template -bcxx --module-name=ConfigSample 
    --module-type=DataFlowComponent 
    --module-desc=Configuration example component --module-version=1.0 
    --module-vendor=Noriaki Ando, AIST --module-category=example 
    --module-comp-type=DataFlowComponent --module-act-type=SPORADIC 
    --module-max-inst=10 --config=int_param0:int:0 
    --config=int_param1:int:1 --config=double_param0:double:0.11 
    --config=double_param1:double:9.9 
    --config=str_param0:std::string:hoge 
    --config=std_param:std::string:dara 
    --config=vector_param0:std::vector<double>:0.0,1.0,2.0,3.0,4.0
 
    # 실제로는 1행으로 입력하던지 계속 문자를 행의 마지막에(UNIX에서는 \, Windows에서는 ^)을 입력해 주십시오.

이것은 샘플에 부속되어 있는 ConfigSample에서의 지정 예입니다.

 --config=<명칭>:<데이터 형>:<디폴트 값>

와 같이 지정합니다. 데이터 형에 대해서는 그 언어에서 사용하는 데이터 형을 지정합니다만, 프리미티브 형 이외에서는 제대로 동작하지 않거나 수동으로 수정이 필요한 경우가 있습니다.

수동에 의한 정의

별로 추천하지 않습니다만 수동으로도 컨피그레이션 파라메터를 정의 하는 것이 가능합니다. 새로 파라메터를 추가하고 싶은 경우 등에 유효합니다만 문서나 RTC.xml파일 등을 갱신하지 않으면 제3자가 이 RTC를 사용 하는 경우에 사양과 구현의 정합성을 이해할 수 없거나 혼란을 초래할 가능성 이 있기 때문에 주의해 주십시오.

단, 컨피그레이션이 어떻게 선언되어 사용되는지를 이해하는 것은 의미가 있기 때문에 여기서 설명합니다.

컨피그레이션을 사용하려면 아래의 작업이 필요합니다.

컨피그레이션 파라메터(이하 파라메터)의 용도, 이름, 데이터 타입을 결정

위에서 설명한 것처럼 컴포넌트의 어느 부분에서 파라메터를 사용하는지 또는 그 파라메터의 특징을 나타내는 이름과 구현시의 데이터 타입명(타입이 있는 언어의 경우)을 결정합니다.

파라메터의 변수를 컴포넌트의 헤더(private/protected)에 선언

RTCBuilder나 rtc-template에서 생성한 파일이면, 이하와 같은 태그로 둘러싸인 부분이 있습니다. 여기에 컨피그레이션 파라메터를 위한 변수를 선언합니다.

  // Configuration variable declaration
  // <rtc-template block="config_declare">
 
  // </rtc-template>

위의 ConfigSample의 예라면 이하와 같이 됩니다.

  // Configuration variable declaration
  // <rtc-template block="config_declare">
  int m_int_param0;
  int m_int_param1;
  double m_double_param0;
  double m_double_param1;
  std::string m_str_param0;
  std::string m_str_param1;
  std::vector<double> m_vector_param0;
  
  // </rtc-template>

컴포넌트의 구현 파일의 static 변수 <컴포넌트 명>_spec[]에 파라메터의 선언과 디폴트 값을 추가

컨피그레이션 파라메터는 컴포넌트 내에서 Properties라는 데이터 저장소에 저장되어 관리됩니다. 이 Properties안에서는,

 conf.<컨피그레이션 세트 명>.<파라메터 명>

이라는 키로 컨피그레이션 파라메터를 저장하고 있습니다. 디폴트 값으로서 default라는 컨피그레이션 세트명이 예약되어 있어 디폴트 값은 모두 이 default컨피그레이션 세트로서 정의됩니다.

위의 ConfigSample의 경우 이하와 같이 추가 합니다.

 // Module specification
 // <rtc-template block="module_spec">
 static const char* configsample_spec[] =
   {
     "implementation_id", "ConfigSample",
     "type_name",         "ConfigSample",
     "description",       "Configuration example component",
     "version",           "1.0",
     "vendor",            "Noriaki Ando, AIST",
     "category",          "example",
     "activity_type",     "DataFlowComponent",
     "max_instance",      "10",
     "language",          "C++",
     "lang_type",         "compile",
     // Configuration variables
     "conf.default.int_param0", "0",
     "conf.default.int_param1", "1",
     "conf.default.double_param0", "0.11",
     "conf.default.double_param1", "9.9",
     "conf.default.str_param0", "hoge",
     "conf.default.str_param1", "dara",
     "conf.default.vector_param0", "0.0,1.0,2.0,3.0,4.0",
  
     ""
   };
 // </rtc-template>

Configuration variables 이하의 부분이 디폴트 컨피그레이션 세트 의 정의가 됩니다.

각 변수를 초기화자로 초기화

RTCBuilder나 rtc-template로 생성된 변수는 생성자의 초기화자에 의한 초기화는 하지 않지만 가능하면 모든 변수는 생성자의 초기화자로 초기화하는 편이 좋습니다. 또한, 각변수에 디폴트 값이 세트되는 것은 onInitialize()함수의 bindParameter()함수가 불려진 뒤이기 때문에 원칙으로는 그 이전에는 사용해서는 안됩니다.

bindParameter()함수로 파라메터와 변수를 바인드

마지막으로 변수와 파라메터의 이름, 디폴트 값, 변환 함수를 바인드 하는 것으로 단순한 변수를 컨피그레이션 파라메터로 합니다. RTObject클래스의 멤버 함수(메소드)인 bindParameter()를 사용합니다.

 bindParameter(<파라메터 이름(문자열)>, 변수, <디폴트 값(문자열)>, <변환 함수>)

위의 ConfigSample(C++의 예)에서는 이하와 같이 됩니다.

  // <rtc-template block="bind_config">
  // Bind variables and configuration variable
  bindParameter("int_param0", m_int_param0, "0");
  bindParameter("int_param1", m_int_param1, "1");
  bindParameter("double_param0", m_double_param0, "0.11");
  bindParameter("double_param1", m_double_param1, "9.9");
  bindParameter("str_param0", m_str_param0, "hoge");
  bindParameter("str_param1", m_str_param1, "dara");
  bindParameter("vector_param0", m_vector_param0, "0.0,1.0,2.0,3.0,4.0");
  
  // </rtc-template>

이렇게 하는 것으로 각 변수와 컨피그레이션 파라메터가 바인드 되어 RTSystemEditor 등에서 이러한 변수를 조작할 수 있는 컨피그레이션 파라메터가 이용 가능하게 됩니다.

또한, bindParameter()에 파라메터로 주는 변환 함수는 임베디드 형에 대해서는 상기의 예와 같이 불필요하고 특히 명시적으로 줄 필요는 없습니다. 하지만 독자적인 구조체나 복잡한 형등을 컨피그레이션 파라메터로서 사용하고 싶은 경우는 문자열로 그러한 형태로의 변환을 정의해 여기에 줄 필요가 있습니다. 변환 함수의 자세한 내용은 뒤에서 설명합니다.

파라메터를 사용

파라메터를 사용하는 것은 매우 간단합니다. 지금까지 설명한 대로 컨피그레이션 파라메터로서 선언된 변수를 단순히 사용하는 것 뿐입니다. 단, 사용에 대해서는 몇가지의 조건이 있어, 이 조건을 지켜서 사용해야 합니다.

변수를 사용할 수 있는 콜백 함수

컨피그레이션 변수는 특정의 콜백 함수 (onXXX())내에서 밖에 사용할 수 없습니다. 외부에서부터의 컨피그레이션 변수의 변경은 비동기적으로 행해집니다. 일반적으로 이러한 경우는 뮤텍스 등으로 변수에의 배타 액세스 제어를 할 필요가 있습니다만, 이것을 실현하려면 컴포넌트 개발자도 각 변수에의 액세스 시에 뮤텍스 보호를 해야합니다. 이것을 회피하기 위해 OpenRTM-aist에서는 외부에서의 컨피그레이션의 변경은 콜백 함수의 외부에서 행해지게 되어 있습니다.

사용할 수 있는 콜백 함수는 이하와 같습니다.

  • onInitialize() (※)
  • onActivated()
  • onExecute()
  • onStateUpdate()
  • onDeactivate()
  • onAborting()
  • onError()
  • onReset()
  • onFinalize() (※)

거의 모든 콜백 함수안에서 컨피그레이션 파라메터를 사용 할 수 있습니다. 다만, onInitialize()에 대해서는 bindParameter()를 하기 전에는 당연히 컨피그레이션 파라메터를 사용할 수 없습니다. 또, onFinalize()안에서는 그 호출 직전의 컨피그레이션 파라메터에 대해 변경이 반영되지 않을 가능성이 있습니다.

변수는 읽기 전용

컨피그레이션 파라메터의 변수는 컴포넌트의 외부에서 변경 되어 그 값이 파라메터용 변수에 대입됩니다. 하지만 파라메터용 변수에 onExcute()등 내부 함수내에서 써도 외부에서 보이는 파라메터의 값은 반영되지 않습니다.

이와 같이, 변수의 값은 변경은 일방통행이므로 컴포넌트 내부로부터의 변수에 쓰기는 의미가 없습니다. 컨피그레이션 변수는 read only로 사용합시다.

값이 정상인가 항상 체크

컨피그레이션 파라메터 값은 위에서 설명한 대로 외부에서 문자열로서 주어진 것을 변환 함수로 변환한 것이 실제 사용되는 변수에 대입됩니다. 문자열이기 때문에 본래 수치가 대입되어야 할 곳에 문자열이 대입되거나 short int로 정의된 변수에 상한 이상의 크기의 수치가 대입되는 것도 있을 수 없습니다. 따라서, 받은 측에서는 변수가 상정되어 있는 값의 범위내에 들어가 있는지, 있을 수 없는 값이 대입되어 있지는 않은지 프로그램 상에서 사용전에 항상 체크하는 것을 추천합니다.

파라메터를 설정

컨피그레이션 파라메터는 몇개의 세트를 갖고 실행시에 그것들을 동시에 변경할 수 있다는 것을 위에서 설명했습니다. 한편, RTCBuilder나 rtc-template에서 컴포넌트를 설계하는 시점에서는 디폴트 컨피그레이션 세트 밖에 정의 할 수 없었습니다. 여기에서는 컨피그레이션 세트의 사용법에 대해 설명합니다.

컴포넌트 설정 파일

디폴트 컨피그레이션 세트는 소스 코드에 적용됩니다. 같은 방법으로 다른 컨피그레이션 세트도 원리적으로는 소스 코드에 적용하는 것이 가능합니다. 그러나 RTC컨피그레이션 기능의 목적은 소스 코드를 변경하지 않고 용도에 따라 파라메터를 변경하는 것으로 하나의 컴포넌트를 다양한 용도로 사용하는 것이기 때문에, 소스 코드에 다른 컨피그레이션 세트를 적용하는 것은 본말 전도입니다.

컨피그레이션 세트는 컴포넌트의 컨피그레이션 파일로 줄 수 있습니다. 컴포넌트의 설정을 하는 파일에는 rtc.conf가 있습니다만 이것은 주로 컴포넌트를 관리하는 미들웨어를 위한 설정 파일로 컴포넌트를 위한 설정 파일은 rtc.conf내에 이하와 같이 지정할 수 있습니다.

 corba.nameservers: localhost
 naming.formats: %h.host_cxt/%n.rtc
 example.ConfigSample.config_file: configsample.conf

example.ConfigSample.config_file의 부분이 컴포넌트의 컨피그레이션 파일의 지정 부분입니다. 컨피그레이션 파일을 지정하는 부분은 이하와 같습니다.

 <카테고리명>.<모듈명>.fongi_file: <파일명>

또, 컴포넌트의 모듈명 대신에 인스턴스명을 주는 것도 가능합니다.

 <카테고리명>.<인스턴스명>.fongi_file: <파일명>

따라서, 인스턴스 마다 다른 컨피그레이션 파일을 주는 것도 가능합니다.

 example.ConfigSample0.config_file: consout0.conf
 example.ConfigSample1.config_file: consout1.conf
 example.ConfigSample2.config_file: consout2.conf

컨피그레이션 세트의 설정

컨피그레이션 파일안에는 사용하고 싶은 컨피그레이션 세트를 기술합니다.

 configuration.active_config: mode1
 
 conf.mode0.int_param0: 12345
 conf.mode0.int_param1: 98765
 conf.mode0.double_param0: 3.141592653589793238462643383279
 conf.mode0.double_param1: 2.718281828459045235360287471352
 conf.mode0.str_param0: mode0
 conf.mode0.str_param1: foo
 conf.mode0.vector_param0: 0.0,0.1,0.2,0.3,0.4
 
 conf.mode1.int_param0: -999
 conf.mode1.int_param1: 999
 conf.mode1.double_param0: 297992458
 conf.mode1.double_param1: 2.97992458e+8
 conf.mode1.str_param0: mode1
 conf.mode1.str_param1: AIST
 conf.mode1.vector_param0: 1,2,3,4,5,6,7,8,9
 
 conf.__widget__.int_param0: slider.1
 conf.__widget__.int_param1: spin
 conf.__widget__.double_param0: slider.0.1
 conf.__widget__.double_param1: text
 conf.__widget__.str_param0: radio
 conf.__widget__.str_param1: text
 conf.__widget__.vector_param0: text
 
 conf.__constraints__.int_param0: 0<=x<=150
 conf.__constraints__.int_param1: 0<=x<=1000
 conf.__constraints__.double_param0: 0<=x<=100
 conf.__constraints__.double_param1: 
 conf.__constraints__.str_param0: (default,mode0,mode1,foo,bar,radio)
 conf.__constraints__.str_param1: 
 conf.__constraints__.vector_param0: 

액티브 컨피그레이션 세트의 지정

맨 처음의 행 configration.active_config에 액티브한 컨피그레이션 세트 명을 지정하고 있습니다. 여기에서는 mode1이라는 세트명으로 당연히 존재하는 세트명을 지정할 필요가 있습니다.

 configuration.active_config: mode1

컨피그레이션 세트의 설정

다음으로 conf.mode0로 시작하는 파라메터의 리스트가 있습니다만 이것이 세트명 mode0의 컨피그레이션 파라메터의 리스트입니다. 지정의 방법은 소스 코드와 거의 같이

 conf.<세트명>.<파라메터명>: <디폴트값>

로 되고 있습니다. 반드시 존재하는 모든 컨피그레이션 파라메터에 대해 지정해 주십시오. 지정되지 않은 경우는 디폴트 값이 사용됩니다. 그 다음으로 conf.mode1로 시작하는 파라메터의 리스트가 있습니다만 이것도 mode0같이, mode1이라는 세트명의 파라메터의 설정입니다.

확장 기능

다음에 conf, _ _widget_ _으로 시작하는 설정 리스트가 있습니다. 이것은 RTSystemEditor에서 사용되는 특수한 파라메터입니다. RTCBuilder로 컨피그레이션 파라메터를 설정할 때 widget을 지정할 수 있는 것을 위에서 설명했지만 여기에서 설정된 내용이 conf._ _widget_ _으로 설정됩니다. slider, radio, spin, text의 4종류를 설정할 수 있고 각각 RTSystemEditor에서 컨피그레이션 파라메터 설정 다이얼로그를 열었을때 슬라이더, 라디오버튼, 스핀 버튼, 텍스트 박스로 파라메터를 조작할 수 있습니다.

 conf.__widget__.<파라메터명>: 위젯명

또한, 슬라이더(slider)를 설정한 경우,

 conf.__widget__.int_param0: slider.5

와 같이 하는 것으로 슬라이더의 조작폭을 5로 할 수 있습니다. 현재, 이 조작폭을 소수로 하는 것은 불가능합니다. 단, 이후의 버전업에서 개선될 가능성이 있습니다. 또한, 스핀 버튼은 그 성질상 항상 1입니다. int 등 정수치의 파라메터에만 사용하는 것을 추천합니다.

이것들 conf._ _widget_ _파라메터를 설정한 경우, 그 아래의 conf._ _constraints_ _파라메터를 설정할 필요가 있습니다. conf._ _constraints_ _파라메터는 값의 범위를 설정하기 위한 특수한 파라메터로 지정 방법으로는 슬라이더나 스핀 버튼을 위한 가변수 x와 등호, 부등호를 이용한 다음과 같은 지정 방법이나,

 conf.__constraints__.int_param0: 0<=x<=150

radio 버튼에 의한 괄호와 콤파에 의한 리스트에 의한 이하와 같은 지정 방법이 있습니다.

 conf.__constraints__.str_param0: (default,mode0,mode1,foo,bar,radio)

이 외에도 conf._ _constraints_ _파라메터에는 제약의 기술 방법잉 있습니다만, 위젯에 사용되는 제약 조건의 지정 방법은 현재 상기의 2개 뿐입니다.

변환 함수에 대해

C++등에서는 int나 douboe등의 임베디드 형에 대해서는 특히 변환 함수를 지정할 필요가 있습니다. 한편, 구조체나 STL컨테이너 등 유저 독자적인 형을 이용하고 싶은 경우도 있습니다. 이 경우, 문자열에서 각각의 형으로의 변환을 어떻게 할까를 bindParameter()에 함수로서 줄 필요가 있습니다.

변환 함수에 대해서는 이하와 같이 각 언어별로 룰이 있습니다. 이하는 각 언어별 방법을 기술합니다.

C++의 경우 변환 함수

C++에 있어서 bindParameter의 프로토 타입 선언은

 template <typename VarType>
     bool bindParameter(const char* param_name, VarType& var,
                const char* def_val,
                 bool (*trans)(VarType&, const char*) = coil::stringTo)
               

와 같이 되어 있어 제4인수의 trans에 적당한 함수 포인터를 주는 것으로 문자열에서 해당형에의 변환이 행해집니다. 디폴트로는 coil라이브러리 함수의 stringTo()함수가 주어지고 있습니다. 스스로 이 stringTo()에 해당하는 변환 함수를 쓰고, 함수 포인터를 줄 수도 있습니다만 coil::stringTo()함수 자체도 함수 템플릿으로 되어 있어, std::stream에 대한 operator>>()함수

 std::istream& operator>>(std::istream&, T)

가 정의되어 있다면 자동적으로 이것을 사용해 문자열에서 특정형으로의 변환이 행해집니다.

즉, std::cin >> <어떤형의 변수>와 같이 쓰는 방법이 가능하다고 한다면 그 형은 operator>>()가 정의되어 있어 특히 변환 함수를 쓰지 않아도 컨피그레이션 의 파라메터로서 사용할 수 있습니다.

만약, 변환 함수가 없는 경우, 예를 들어 이하와 같은 콤마 구분의 수치 열

 0.0,1.0,2.0,3.0,4.0

을 std::vector<double>로 변환하기 위한 변환 함수는,

 #include <istream>
 #include <ostream>
 #include <vector>
 #include <string>
 #include <coil/stringutil.h>
 
 template<typename T>
 std::istream& operator>>(std::istream& is, std::vector<T>& v)
 {
   std::string s;
   std::vector<std::string> sv;
   is >> s;
   sv = coil::split(s ,",");
   v.resize(sv.size());
   for (int i(0), len(sv.size()); i < len; ++i)
     {
       T tv;
       if (coil::stringTo(tv, sv[i].c_str()))
         {
           v[i] = tv;
         }
     }
   return is;
 }

이와 같이 구현할 수 있습니다. 또한, 이것은 OpenRTM-aist C++버전의 샘플, ConfigSample 컴포넌트의 소스에 포함되는 VectorConver.h입니다.

이것을 bindParameter()가 호출되는 소스(예를 들면, ConfigSample 컴포넌트 라면 ConfigSample.cpp), 통상은 컴포넌트의 구현 소스로 include해 주면, 컴파일시에 컴파일러가 판단하여 적당한 변환 함수가 사용됩니다.

Java의 경우 변환 함수

Java의 경우 변환 함수라는 것을 별도로 주는 것이 아니라, 컨피그레이션 변수의 홀더 클래스에 있어 정의되는 stringFrom() 메소드에 문자열 로부터 실제의 형으로 변환을 기술합니다.

이하에 OpenRTM-aist java버전의 ConfigSample에 정의되어 있는 콤마 구분 수치값을 Vector형으로 변환 하기 위한 변환 함수를 나타냅니다.

 package RTMExamples.ConfigSample;
 
 import java.io.Serializable;
 import java.util.Vector;
 
 import jp.go.aist.rtm.RTC.util.ValueHolder;
 
 public class VectorHolder  implements ValueHolder, Serializable {
 
     /**
      * Vector형 데이터 설정값
      */
     public Vector value = null;
 
     /**
      * 디폴트 생성자
      *
      */
     public VectorHolder() {
     }
 
     /**
      * 생성자
      *
      * @param initialValue 초기값
      *
      */
     public VectorHolder(Vector initialValue) {
         value = new Vector(initialValue);
     }
 
     /**
      * 문자열에서 Vector형르로 변환해 설정
      *
      * @param def_val 설정값 문자열 표현
      *
      */
     public void stringFrom(String def_val) throws Exception {
         value = new Vector();
         String values[] = def_val.split(",");
         for( int intIdx=0;intIdx<values.length;intIdx++ ) {
             value.add(values[intIdx]);
         }
     }
     /**
      * 설정값의 취득
      *
     * @return 설정값
      *
      */
     public Vector getValue(){
         return value;
     }
     /**
      * 설정값을 문자열로 변환
      *
     * @return 변환 문자열
      *
      */
     public String toString(){
         StringBuffer retVal = new StringBuffer();
         while(value.iterator().hasNext()) {
             retVal.append(value.iterator().next());
             if(value.iterator().hasNext()) retVal.append("'");
         }
         return retVal.toString();
     }
 }

Python의 경우 변환 함수

Python버전 OpenRTM-aist에서는 디폴트에서는 기본형과 그 리스트에 대응해 그 외의 변환이 필요하면, bool stringTo(type, string)와 같은 함수를 정의하고 bindParameter()의 제4인수로 함수 오브젝트를 건네줍니다.

무엇을 파라메터로 하는가?

RT컴포넌트를 작성하는데 무엇을 컨피그레이션 파라메터로 하면 좋은 것인지 생각해 봅시다.

어느 파라메터가 있어 그것을 외부로부터 변경하려면 몇개의 방법이 생각될 수 있습니다. 데이터 포트를 이용해 변경하는 방법, 서비스 포트를 이용해 변경하는 방법 그리고 컨피그레이션을 이용해 변경하는 방법입니다.

컨피그레이션 기능은 컴포넌트 내부의 파라메터를 변경하기 위한 기능입니다. 따라서 로직내의 파라메터는 컨피그레이션 파라메터로서 외부에서 변경할 수 있도록 해야 합니다. 그러나 어떤 변경량을 컨피그레이션 파라메터로 해야 하는가 그렇지 않은 것인가 망설이는 경우 도 있다고 생각합니다. 여기에서는 그러한 경우에 대해 잠시 생각해 보겠습니다.

갱신 빈도

컨피그레이션 파라메터는 일반적으로 시스템이 움직이기 시작하기 전에 1번, 혹은 설정 변경이 필요하게 된 경우만 외부에서 파라메터를 주기 위해 이용합니다. 갱신 빈도가 시스템의 라이프 사이클상에서 1회내지는 몇차례정도면 컨피그레이션을 사용하는 것이 좋을 것입니다.

또한 위에서도 설명했지만 컨피그레이션은 툴이나 어플리케이션에서는 문자열로서 주어지기 때문에 컴포넌트내에서 각각의 형태로 변환됩니다. 변환에는 어느 정도(최근의 PC에서는 수 us에서 수백 us정도입니다만) 시간이 걸리기 때문에 예를 들면 1ms주기로 데이터를 보내는 용도로는 적합하지 않습니다. 그럼 그 정도의 빈도로 파라메터를 변경할 수 있는 것일까요? 실제로 사용할 때에는 파라메터의 수나 컴퓨터, 네트워크의 속도에도 의존하지만, 수백ms 또는 그 이상의 빈도에서는 사실상 문제없이 파라메터를 변경할 수 있습니다. 단, 그와 같은 주기적으로 몇번이나 값을 변경할 필요가 있는 것은 데이터 포트를 사용 해야 할 것입니다.

데이터인가 파라메터인가?

예들 들면, 원격지의 센서에서 정기적으로 데이터를 중앙 서버에 보내는 시스템을 생각해봅시다. 데이터는 1시간에 1회 보내져 서버측에서는 그것을 로그에 기록한다고 합니다. 이 때, 그 데이터는 데이터 포트를 사용해 보내야 할 것인가? 아니면 서비스 포트를 사용해야만 하는 것인가, 혹은 컨피그레이션을 사용해야 할 것인가?

보내지는 것은 센서의 데이터이기 때문에 데이터 포트를 이용해 보내는 것이 가장 적합하다고 할 수 있습니다. 컨피그레이션은 외부에서 파라메터를 설정하기 위한 구조이므로 비록 갱신 빈도가 1시간에 1번이라도 이 데이터를 컨피그레이션으로 컴포넌트에 전송하는 것은 부적절하다고 할 수 있습니다. 단, 데이터 포트에서는 실현될 수 있는 클라이언트와 서버측의 복잡한 교환(트랜잭션 등)을 실현하고 싶은 경우는 서비스 포트가 사용될지도 모릅니다.

서비스인가 파라메터인가?

데어터 포트로 해야 하는가 컨피그레이션으로 해야 하는가는 실제로 그다지 망설일 것은 아닐 것 입니다. 한편, RTC로직내에서의 파라메터를 서비스 포트에서 변경해야 할 것인가 컨피그레이션 파라메터로 해야 할 것인가 망설이는 경우는 많을 것으로 생각합니다.

컴포넌트가 어떤 종류의 전형적이고 어느 정도 결정된 기능을 제공하는 경우, 그 기능은 서비스 포트의 인터페이스에 의해 외부에 제공됩니다. 서비스 인터페이스에서는 대상의 상태를 취득하거나 설정・모드・파라메터를 변경하거나 하기 위한 오퍼레이션을 제공합니다. 상태의 취득은 예외지만, 설정을 하거나 모드・파라메터를 변경하거나 하는 기능은 컨피그레이션과 매우 닮았습니다.

결국은 어디에서 설정해도 큰 차이는 없습니다만, 대상으로 하는 RTC의 기능이 이미 서비스 인터페이스로서 정의되어 있거나 상태의 취득과 설정이 필요하게 되는 등 어는 정도 복잡한 기능을 제공하는 경우, 서비스 인터페이스를 매개로한 조작이 적합하다고 말할 수 있습니다. 그 이외의 간단한 파라메터・ 모드 등의 설정에는 컨피그레이션을 이용하면 좋을 것입니다.

정리

여기에서는 컨피그레이션 기능에 대한 정의 방법이나 사용법에 대해 설명하였습니다. 로직내의 파라메터는 컴포넌트의 재사용성을 향상 시키기 위해 가능한한 이 기능을 사용해 외부화 해야 합니다. 무엇을 컨피그레이션 파라메터로 해야 하는가, 해서는 안되는가 라는 것에 대해서도 주의를 기울일 필요가 있습니다. 컨피그레이션 기능을 유효하게 사용하면 작성하는 컴포넌트도 재사용성이 높아질 것입니다.

설정 파일 (기초편)

컨피귤레이션

rtc.conf

컴퍼넌트 매니저는 기동시로 설정 파일 rtc.conf를 읽어들입니다. 컨피귤레이션 파일은 통상 rtc.conf라는 이름으로 작성합니다만, 임의의 이름으로 작성한 컨피귤레이션 파일을 건네줄 수도 있습니다.

네임 서비스에 관한 설정

네이밍 서비스의 설정에 관한 항목은 이하와 같습니다.

corba.nameservers
host_name:port_number로 지정, 디폴트 포토는 2809(omniORB의 디폴트).
::복수 서버를 지정 가능하고, 서버명의 단락 문자는 콤마 "," .
naming.formats
%h.host_cxt/%n.rtc →host.host_cxt/MyComp.rtc
::복수 지정 가능.
::0.2.0 호환으로 하고 싶으면,
:::%h.host_cxt/%M.mgr_cxt/%c.cat_cxt/%m.mod_cxt/%n.rtc
naming.update.enable
“YES” or “NO”
::네이밍 서비스에의 등록의 자동 업데이트 설정.
::컴퍼넌트 기동 후에 네임 서비스가 기동했을 때에, 재차 이름을 등록한다.
naming.update.interval
업데이트의 주기[s].디폴트는 10초.
timer.enable
“YES” or “NO”
매니저 타이머 유효·무효.naming.update를 사용하려면 유효하지 않으면 안 된다.
timer.tick
타이머의 분해가능[s].디폴트는 100ms.

로그 출력에 관한 설정

logger.enable
“YES” or “NO”
::로그 출력을 유효·무효로 설정.
logger.file_name
로그 파일명.
::%h:호스트명,%M:매니저명, %p:프로세스 ID 사용가능
logger.date_format
일자 포맷.strftime(3)의 표기법으로 준거.
::디폴트:%b %d %H:%M:%S → Apr 24 01:02:04
logger.log_level
로그 레벨: SILENT, ERROR, WARN, NORMAL, INFO, DEBUG, TRACE, VERBOSE, PARANOID.
::아무것도 출력하지 않는(SILENT) 모두 출력한다(PARANOID).
::※이전에는 RTC내에서 사용할 수 있었습니다만, 현재는 아직 사용할 수 없습니다.

실행 문맥에 관한 설정

exec_cxt.periodic.type
사용하는 실행 문맥을 지정.
::현재로서는,
PeriodicExecutionContext, ExtTrigExecutionContext
하지만 사용 가능.
::디폴트는 PeriodicExecutionContext.
exec_cxt.periodic.rate
실행 문맥의 주파수[Hz]를 지정.
::유효 범위:(0, 1000000].
::디폴트:1000.

그 외의 설정

corba.endpoint
IP_Addr:Port 로 지정.NIC가 다수 있을 때, ORB를 어디에서 listen 시키는지를 지정한다.
::Port를 지정하지 않는 경우에서도:가 필요.
::예: corba.endpoint: 192.168.0.12:
::NIC가 2개 있는 경우 반드시 지정해 주세요.
(지정하지 않아도 우연 정상적으로 동작하기도 한다.)
corba.args
CORBA에 대한 인수.자세한 것은 omniORB의 메뉴얼 참조.
[카테고리명].[컴퍼넌트명].config_file
[카테고리명].[인스턴스명]. config_file
컴퍼넌트의 설정 파일
카테고리명:manipulator, 컴퍼넌트명:myarm, 인스턴스명 myarm0,1,2,… 의 경우
 manipulator.myarm.config_file: arm.conf
 또는
 manipulator.myarm0.config.file: arm0.conf
(와)과 같이 지정 가능

매니저 (기초편)

rtc.conf 설정 항목 일람

일반적인 설정

config.version

컨피귤레이션 파일의 버젼.
  • 례:
     config.version: 1.0

openrtm.version

OpenRTM-aist 의 버젼.
  • 례:
     openrtm.version: 1.0.0

manager 에 관한 설정

manger.name

manager의 이름.매니저가 네임서버로 등록될 때 , 여기서 설정해 이름으로 등록된다.

  • 지정: 네임서버등에 등록 가능한 임의의 이름
  • 디폴트치: manager
  • 례:
     manager.name: manager

maanger_naming_formats

매니저를 네임서버에 등록할 때의 포맷을 지정한다.이하의 %로 시작되는 지정자를 이용할 수 있다.

지정자 의미
%n 매니저명
%h 호스트명
%M 매니저명
%p 매니저의 프로세스 ID
  • 지정: /<name>.<kind>/<name>.<kind>/...
  • 디폴트치: %h.host_cxt/%n.mgr
  • 례:
     manager.name: %h.host_cxt/%n.mgr

manager.is_master

해당 프로세스를 마스터 매니저로 할지?커멘드 라인 오프쇼

 ''-d''를 지정하면, 이 값이 NO 로 설정되어 있어도 마스터 매니저
(이)가 된다.

  • 지정: YES or NO
  • 디폴트치: NO
  • 례:
     manager.is_master: NO

manager.corba_servant

매니저의 CORBA 서번트를 기동할지의 설정.YES 를 설정하면, 매니저의 CORBA 서번트가 기동하기 위해(때문에), 리모트로부터 매니저의 조작 하지만 가능하게 된다.NO 의 경우에는, CORBA 서번트가 기동하지 않기 때문에, 매니저 의 CORBA 경유로의 조작은 할 수 없게 된다.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례:
     manager.corba_servant: YES

corba.master_manager

마스터 매니저의 주소와 포토 번호.마스터 매니저는, corbaloc 형식의 URL 지정으로 액세스 가능하지만, 그 때에 사용하는 포토차례 호를 지정한다.또, 슬레이브 매니저는, 여기서 지정된 마스터 머니 쟈를 자신의 마스터 매니저로서 해석, 기동시에 마스터 매니저에 액세스 해 네고시에이션을 실시한다.

  • 지정: <host_name>:<port>
  • 디폴트: localhost:2810
  • 례:
     corba.master_manager: localhost:2810

manager.shutdown_on_nortcs:

프로세스상에 RTC가 하나도 없어졌을 경우, 즉 동일 프로세스상의 RTC의 최 후의 하나가 종료했을 경우에, 매니저를 슛다운 해 해당 프로세스를 종료 시킬지를 지정한다.YES 의 경우에는, RTC가 하나도 없어진 시점에서 프 로세스가 종료한다.NO의 경우는, RTC가 하나도 없는 상태에서도 매니저, 프로세 스 모두 계속 움직인다.

  • 지정: YES or NO
  • 디폴트: YES
  • 례:
     manager.shutdown_on_nortcs: YES

manager.shutdown_auto

프로세스내의 RTC의 유무를 일정시간 마다 조사해 RTC가 없는 경우에는, 매니저 및 프로세스를 슛다운 할지를 설정한다.YES의 경우, RTC가 하나도 없으면, 매니저 및 프로세스는 자동적으로 슛다운 된다. NO의 경우, RTC가 하나도 없어도 매니저 및 프로세스가 계속 동작한다.

manager.shutdown_on_nortcs 와의 차이는, 슛다운의 방아쇠가, manager.shutdown_on_nortc 에서는 RTC의 삭제인데 대하고, manager.shutdown_auto 는 시간이 되고 있는 점이다.
  • 지정: YES or NO
  • 디폴트: YES
  • 례:
     manager.shutdown_auto: YES

manager.supported_languages

마스터 매니저는, 리모트 어플리케이션등에서의 요구에 따르고, 슬레이브 매니저 및 RTC를 기동한다.슬레이브 매니저는, C++언어판 뿐만이 아니고, Java판, Python판등의 가능성도 있다.이 옵션에서는, 마 스타 매니저가 서포트하는 언어를 설정한다.

  • 지정: C++, Java, Python 등의 언어를 콤마 단락으로 지정
  • 디폴트: C++, Java, Python
  • 례:
     manager.supported_languages: C++, Python, Java

manager.modules.<lang>.suffixes

언어마다의 RTC 모듈의 확장자(extension).

  • 지정: 공유 오브젝트의 확장자(extension)명
  • 디폴트:
    • Windows: dll
    • Linux등: so
    • Mac OS X: dylib
  •  manager.modules.C++.suffixes: dll
     manager.modules.C++.suffixes: so
     manager.modules.C++.suffixes: dylib

manager.modules.<lang>.manager_cmd

언어마다의 매니저 프로그램명.

  • 지정: 매니저의 커멘드명
  • 디폴트:
    • C++: rtcd
    • Python: rtcd_python
    • Java: rtc_java
  •  manager.modules.C++.manager_cmd: rtcd
     manager.modules.Python.manager_cmd: rtcd_python
     manager.modules.Java.manager_cmd: rtcd_java

manager.modules.<lang>.profile_cmd

언어마다의 프로파일 취득 커멘드명.

  • 지정: 프로파일 취득 커멘드명
  • 디폴트:
    • C++: rtcprof
    • Python: rtcprof_python
    • Java: rtc_java
  •  manager.modules.C++.profile_cmd: rtcprof
     manager.modules.Python.profile_cmd: rtcprof_python
     manager.modules.Java.profile_cmd: rtcprof_java

manager.modules.<lang>.load_paths

언어마다의 RTC 모듈 로드 패스.

  • 지정: RTC 모듈 로드 패스.
  • 디폴트:
    • C++: ./
    • Python: ./
    • Java: ./
  •  manager.modules.C++.profile_cmd: ./, /usr/share/OpenRTM-aist/components/cxx

CORBA에 관한 설정

corba.args

CORBA에게 주는 인수를 지정한다.CORBA 는 실장마다 다른 커멘드 라인 오프쇼 를 가진다.통상 커멘드 라인 인수는, CORBA 의 API 인 ORB_init() 함수 에게 줄 수 있지만, 이 옵션은 지정된 문자열을 이 ORB_init() 함수 에 건네준다.

  • 지정: 문자열
  • 디폴트: 공문자열
  • 례:
     corba.args: -ORBInitialHost myhost -ORBInitialPort 8888

corba.endpoint:

CORBA에 대해서는, 리모트의 오브젝트의 IOR로 불리는 참조에 의해 악세 스 하지만, IOR에는 해당 오브젝트가 동작하는 노드의 주소와 포토 번호 하지만 통상 1 세트만 기술되고 있다.OpenRTM가 동작하고 있는 노드에 2개 이상의 네트워크 인터페이스가 존재하는 경우, IOR에 포함되는 노드의 아드레 스로서 의도하지 않는 주소가 할당해지는 경우가 있다.

이것을 해소하기 위해서, 본옵션으로 CORBA로 이용하는 네트워크의 아드레 스를 지정할 수 있다.호스트 주소:포토 번호로서 지정하지만, 포토 번호는 생략 할 수 있다.다만,:(코론)는 생략 할 수 없다.

  • 지정: <host_addr>:<port>
  • 디폴트: 공문자
  • 례:
     corba.endpoint: 192.168.0.45:
     corba.endpoint: 192.168.0.45:8776
     corba.endpoints: myhost:      (use myhost and default port)
     corba.endpoints: :9876        (use default addr and port 9876)
     corba.endpoints: myhost:9876  (use myhost and port 9876)

corba.endpoints

corba.endpoint 의 엔드 포인트를 복수 지정하는 것이 오는 옵션.ORB 의 실장에 따라서는, IOR에 복수의 주소를 포함할 수 있다.다만, Java 표준의 CORBA인 JavaIDL에 대해서는, 복수의 주소를 지정한 IOR 경유 그리고 해당 오브젝트에 액세스 하는 경우, 동작이 늦어지는 등 문제도 보고되어 (이)라고 있으므로 주의가 필요하다.

주소:포토의 대를 ,(콤마)로 단락 복수 지정하는 것이 성과 .특별한 문자열로서 all를 지정하는 것으로, 노드의 모든 아드레 스를 IOR에 포함할 수도 있다.

  • 지정: <host_addr>:<port>, <host_addr>:<port>, ... 또는 all
  • 디폴트: 공문자
  • 례:
     corba.endpoints: 192.168.1.10:1111, 192.168.10.11:2222
     corba.endpoints: 192.168.1.10, 192.168.10.11
     corba.endpoints: all

corba.endpoints:

corba.nameservers

RTC등을 등록하는 네임서버를 지정하는 옵션.콤마 단락으로 복수의 네 무서바를 지정할 수 있다.지정한 주소 및 포토 번호에 네 무서바가 없는 경우에서도 특히 에러에는 안되어, 존재하는 네임서버에게만 RTC의 이름을 등록한다.

  • 지정: <host_addr>:<port>, <host_addr>:<port>, ...
  • 디폴트: localhost
  • 례:
     corba.nameservers: openrtm.aist.go.jp:9876
     corba.nameservers: rtm0.aist.go.jp, rtm1.aist.go.jp, rtm2.aist.go.jp
     corba.nameservers: localhost

corba.nameservice.replace_endpoint

노드에 복수의 NIC가 존재하는 경우, 네임서버상에 등록되는 RTC의 IOR에 함 주소가, 적절하지 않은 경우가 존재한다.예를 들면, 어느 노드가 192.168.0.10으로 192.168.1.10이라고 하는 2개의 주소를 가져, 192.168.0.1 및 192.168.1.1 에 존재하는 2개의 네임서버상에 등록되는 경우, 만일 192.168.0.10 이 해당 노드로 디폴트로 이용되는 네트워크 인터 페이스라고 하면, 상기 2개의 네임서버 네임서버에 등록되는 IOR에는, 192.168.0.10 만이 포함된다.이 때, 192.168.1.0 의 네트워크에서는 네임서버상의 IOR는 도달 불가능한 주소가 기재된 무의미한 것이 된다.

이 옵션을 지정하면, 상기의 케이스와 같은 경우, 192.168.1.1 의 네 무서바에 등록되는 IOR의 주소를 192.168.1.10 에 옮겨놓는다.

다만, 이 옵션지정 하는 것에 의해서, 192.168.1.0 네트워크상 외 노드에서는, 해당 RTC의 프로파일등을 이용할 수 있지만, 포 트의 접속등은 실시할 수 없다.

  • 지정: YES or NO
  • 디폴트: NO
  • 례:
     corba.nameservice.replace_endpoint: NO

네임 서비스에 관한 설정

naming.enable

이 옵션은 네이밍 서비스에 관한 기능의 유효·무효를 바꾼다. YES를 지정했을 경우, 네임 서비스에 RTC의 참조를 등록한다.NO의 경우, 네 무서비스에의 RTC의 참조의 등록은 행해지지 않는다.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례:
     manager.is_master: NO

naming.type

이 옵션은 네임 서비스의 타입을 지정한다.현재로서는은 corba의 봐를 서포트하고 있다.
  • 지정: 네임 서비스의 타입
  • 디폴트치: corba
  • 례:
     naming.type: corba

naming.formats

RTC를 네임서버에 등록할 때의 포맷을 지정한다.이하의 %로 시 만지정자를 이용할 수 있다.이름 계층의 딜리미터는 /여, 이름 전과 종류(kind)의 딜리미터는 .이다.

%n RTC의 인스턴스명
%t RTC의 타입명
%m RTC의 모듈명
%v RTC의 버젼
%V RTC의 벤더명
%c RTC의 카테고리명
%h 호스트명
%M 매니저명
%p 프로세스 ID
  • 지정: /<name>.<kind>/<name>.<kind>/...
  • 디폴트치: %h.host_cxt/%n.mgr
  • 례:
     naming.formats: %h.host/%n.rtc

naming.update.enable

RTC의 네임서버에의 등록은 통상 인스턴스 생성시에 행해진다.따라서, RTC의 생성 이후에 기동된 네임서버에는, 해당 RTC의 이름과 참조는 등록함 없다.이 옵션을 지정하는 것으로, 정기적으로 네임서버를 확인해, 네임서버의 기동이 확인되었을 경우, 재차 이름과 참조를 등록한다.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례:
     naming.update.enable: YES

naming.update.interval

naming.update.enable 가 YES 의 경우, 네임서버의 확인 및 재등록을 행 주기를 지정한다.

  • 지정: 등록 주기를 [s] 로 지정한다.
  • 디폴트치: 10.0
  • 례:
     naming.update.interval: 10.0

naming.update.rebind

이 옵션에 YES 를 지정하면, 벌써 이름과 참조가 등록되어 있는 네 무서바상에서 이름이 삭제되는 등 했을 경우에도의, 재차 등록을 실시한다.

  • 지정: YES or NO
  • 디폴트치: NO
  • 례:
     naming.update.rebind: NO

모듈 로드에 관한 설정

manager.modules.load_path

매니저는 이 옵션으로 지정된 서치 패스 리스트로부터 모듈을 탐색한다.패스는 콤마 단락으로 열거한다.패스의 딜리미터는, UNIX에서는 /, Windows \\이다.

  • 지정: /dir_name0/dir_name1/..., /dir_name0/dir_name1/...
  • 디폴트치: ./
  • 례:
     manager.modules.load_path: C:/Program Files/OpenRTM-aist,                              C:\\Program Files\\OpenRTM-aist
     manager.modules.load_path: /usr/lib, /usr/local/lib,                                   /usr/local/lib/OpenRTM-aist/libs

manager.modules.preload:

매니저는 기동시에 미리 loadable module를 로드할 수 있다. 이 옵션으로 지정된 loadable module (을)를,manager.modules.load_path로 지정된 서치 패스로부터 찾아낸다. 만약,manager.modules.abs_path_allowed로 YES 가 지정되어 있으면, loadable module를 절대 패스로 지정할 수도 있다.

  • 지정: <module_name>.dll, <module_name>.dll, ...
  • 디폴트치: 공
  • 례:
     manager.modules.preload: ConsoleIn.dll, ConsoleOut.dll
     manager.modules.preload: ConsoleIn.so, ConsoleOut.so
     
     manager.modules.abs_path_allowed: YES
     manager.modules.preload: /usr/lib/OpenRTM-aist/ConsoleIn.so

manager.modules.abs_path_allowed

모듈의 절대 패스 지정 허가 플래그.만약 이 옵션이 YES의 경우, 모듈의 접대 패스 지정이 허가된다.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례: manager.modules.abs_path_allowed: YES

manager.components.precreate

이 옵션은 매니저의 스타트시에 기동하는 컴퍼넌트명 (모듈명)을 지정한다.여기서 지정되는 컴퍼넌트의 팩토리는 manager.module.preload 또는, 매니저에 정적으로 링크 하는 등, 등록되어 있을 필요가 있다.

  • 지정: <module_name>, <module_name>, ...
  • 디폴트치: 공
  • 례:
     manager.components.precreate: ConsoleIn, ConsoleOut, SeqIn, SeqOut

로가-관계의 설정

logger.enable

로가-의 유효화·무효화의 지정.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례:
     logger.enable: YES

logger.file_name

로그 파일명의 지정.콤마 단락으로 복수의 파일에 출력할 수도 성과 .프로세스 ID를 옮겨놓는 지정자 %p 가 이용 가능.또, 파일명 stdout로 하면 로그를 표준 출력 한다.

  • 지정: 패스를 포함한 파일명
  • 디폴트치: ./rtc%p.log
  • 례:
     logger.file_name: /tmp/rtc%p.log
     logger.file_name: /tmp/rtc%p.log, stdout

logger.date_format

로그에 기재하는 일자·시각의 포맷 지정.이하의 strftime(3)를 닮았다 포맷 지정자를 이용 가능.시각을 지정하지 않는 경우,No또는 Disable를 지정한다.

%a abbreviated weekday name
%A full weekday name
%b abbreviated month name
%B full month name
%c the standard date and time string
%d day of the month, as a number (1-31)
%H hour, 24 hour format (0-23)
%I hour, 12 hour format (1-12)
%j day of the year, as a number (1-366)
%m month as a number (1-12).
Note: some versions of Microsoft Visual C++ may use values that range from 0-11.
%M minute as a number (0-59)
%p locale's equivalent of AM or PM
%S second as a number (0-59)
%U week of the year, sunday as the first day
%w weekday as a decimal (0-6, sunday=0)
%W week of the year, monday as the first day
%x standard date string
%X standard time string
%y year in decimal, without the century (0-99)
%Y year in decimal, with the century
%Z time zone name
%% a percent sign
  • 지정: /<name>.<kind>/<name>.<kind>/...
  • 디폴트치: %b %d %H:%M:%S
  • 례:
     logger.date_format: No
     logger.date_format: Disable
     logger.date_format: [%Y-%m-%dT%H.%M.%S%Z]     // W3C standard format
     logger.date_format: [%b %d %H:%M:%S]          // Syslog format
     logger.date_format: [%a %b %d %Y %H:%M:%S %Z] // RFC2822 format
     logger.date_format: [%a %b %d %H:%M:%S %Z %Y] // data command format
     logger.date_format: [%Y-%m-%d %H.%M.%S]

logger.log_level

이하의 로그 레벨을 지정 가능.

  • SILENT
  • ERROR
  • WARN
  • NORMAL
  • INFO
  • DEBUG
  • TRACE
  • VERBOSE
  • PARANOID

쓰는 로그 레벨을 지정했을 때에 실제로 로그에 기록되는 로그 메세지의 레베 르는 이하와 같다.

SILENT completely silent
ERROR includes (ERROR)
WARN includes (ERROR, WARN)
INFO includes (ERROR, WARN, INFO)
NORMAL includes (ERROR, WARN, INFO, NORMAL)
DEBUG includes (ERROR, WARN, INFO, NORMAL, DEBUG)
TRACE includes (ERROR, WARN, INFO, NORMAL, DEBUG, TRACE)
VERBOSE includes (ERROR, WARN, INFO, NORMAL, DEBUG, TRACE, VERBOSE)
PARANOID includes (ERROR, WARN, INFO, NORMAL, DEBUG, TRACE, VERBOSE, PARA)

TRACE, VERBOSE, PARANOID의 각 로그 레벨은 통상 거대한 로그 파일을 생성합니다.PARANOID를 지정하면, 로그 포맷이 무너지는 경우가 있습니다.

  • 지정: (SILENT|ERROR|WARN|NORMAL|INFO|DEBUG|TRACE|VERBOSE|PARANOID)
  • 디폴트치: NORMAL
  • 례:
     logger.log_level: DEBUG

타이머에 관한 설정

timer.enable

타이머 기능을 유효/무효로 한다.타이머를 무효로 하면 타이머를 이용하고 있는 기 능, 예를 들면 네임서버의 정기적 확인과 재등록등이 무효가 된다.

  • 지정: YES or NO
  • 디폴트치: YES
  • 례:
     timer.enable: YES

timer.tick

타이머의 정도를 지정한다.

  • 지정: 타이머의 정도를 [s] 로 지정한다.
  • 디폴트치: 0.1 [s], (= 100ms)
  • 례:
     timer.tick: 1.0

exec_cxt.periodic.type

디폴트의 실행 문맥의 타입.

  • 지정: 디폴트의 실행 문맥명
  • 디폴트치: PeriodicExecutionContext
  • 례:
     exec_cxt.periodic.type: PeriodicExecutionContext
     exec_cxt.periodic.type: ArtExecutionContext

exec_cxt.periodic.rate

디폴트의 실행 문맥의 주기.

  • 지정: 디폴트의 실행 문맥 주기를 [Hz] 로 지정한다
  • 디폴트치: 1000
  • 례:
     exec_cxt.periodic.rate: 100

RT시스템의 개발 (응용편)

인사이드 OpenRTM-aist

데이터포트

InPort·OutPort

버퍼

콜백

인터페이스 확장

RT시스템의 개발입문

RTC프로그래밍 (응용편)

執筆中 (n-ando)

데이터포트 (응용편)

データポート(基本編)では、データポートの基本的な使い方について説明しま した。応用編では、もう少し踏み込んだ使い方について解説します。

新しいデータ型

データポートコールバック

InPort は isNew() でデータの到着の有無を確認して、read() で読みだす、あ るいはOutPort は write() でデータを送り出し、getStatusList() で送信ステー タスを確認する、ということについてはすでに述べました。

例えば、InPort はデータが来てから、onExecute() 等などの関数内で、 isNew() を呼び read() を呼び出すまでデータを取得することはできません。 onExecute() の周期が非常に速かったとしても、データがInPortに到着するタ イミングと、実際に処理が行われるタイミングは非同期に行われます。

データが到着してすぐに、すなわち同期的に処理を行いたい場合にはどうすれ ばよいのでしょうか。これを実現する方法として、OpenRTM-aistではデータポー トやコネクタの種々の処理のタイミングで呼び出されるコールバックを定義し ています。

コールバックには大きく分けて、1) InPort, 2) OutPort, 3) コネクタ, 4) ポー ト の4種類のコールバックが用意されています。

InPortのコールバック

InPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnRead InPort の read() が呼び出された際にコールされる InPort::setOnRead() 関数でセット。
OnReadConvert InPort の read() が呼び出された際にデータを変換するためにコールされる。InPort::setOnReadConvert() 関数でセット。

OnRead コールバックは read() が呼び出されたときに、OnReadConvert は read() が呼び出されたとき、呼び出し元にある種の変換を施したデータを返す ために使用するコールバックです。

それぞれのコールバックは、rtm/PortCallback.h で定義されているそれぞれの ファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };

OnRead を継承した MyOnRead ファンクタでは、コンストラクタで出力ストリー ム std::ostream を渡しています。どこかでオープンしたファイル出力ストリー ム std::ofstream 等を渡すことを意図しています。ファンクタの実体である operator()() では、出力ストリームと標準出力に対して、文字列を出力してい ます。このように、ファンクタでは、予めコンストラクタなどで状態変数を渡 すことで、他のオブジェクトに対する呼び出しも実現することができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(const T&) のみを実装しています。この関数の引数には、read() が呼ばれたときに InPort 変数に読みだされる前のデータが渡されます。この関数内で何らかの処 理を行い return で返したデータは InPort 変数に書き込まれます。この例で は、データ型に data というメンバがあり、かつ乗算演算子が定義されている という前提で自乗を計算して返しています。適切なメンバがない変数型を使用 すればコンパイルエラーになります。

さて、このファンクタを実際にコンポーネントに組み込んでみましょう。 InPort を使用しているサンプルとして、ここでは OpenRTM-aist に含まれてい るサンプルである ConsoleOut を利用します。ConsoleOut は OpenRTM-aist の ソースを展開すると、

 OpenRTM-aist-<version>/examples/SimpleIO/

の下に、また Linux 等でパッケージ等からインストールすると、

 /usr/share/OpenRTM-aist/examples/src/

の下にソースコードがあります。

まず、上記のクラス定義を、ConsoleOut.h に記述します。クラス定義は、本来 別のソースに記述した方が良いのですが、ファンクタクラスは、このコンポー ネント内でしか使用せず、内容も短いものですので、こういう場合はヘッダ内 で実装も含めて定義しても構わないでしょう。

 // ConsoleOut.h
 
   中略
 // Service Consumer stub headers
 // <rtc-template block="consumer_stub_h">
 
 // </rtc-template>
 
 using namespace RTC; 
 
 // ここから追加分
 template <class T>
 class MyOnRead
  : public RTC::OnRead<T>
 {
 public:
   MyOnRead(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "read() 関数が呼ばれました。" << std::endl;
     std::cout << "read() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnReadConvert
  : public RTC::OnReadConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = value.data * value.data;
     return tmp;
   }
 };
 // ここまで追加分
 
 class ConsoleOut
   : public RTC::DataFlowComponentBase
 {
 
   中略
 
  protected:
   // DataInPort declaration
   // <rtc-template block="inport_declare">
   TimedLong m_in;
   InPort<TimedLong> m_inIn;
 
   中略
 
  private:
   //ここから追加分
   MyOnRead<TimedLong>* m_onread;
   MyOnReadConvert<TimedLong>* m_onreadconv;
   //ここまで追加分
 };

まず、ConsoleOut クラスの宣言の前に、コールバックファンクタ MyOnRead と MyOnReadConvert を宣言します。これらのクラスのポインタ変数をメンバとし て持たせるために、private の部分に、それぞれのポインタ変数を宣言します。 このとき、MyOnRead/MyOnReadConvert ともに、クラステンプレートの型引数に このコンポーネントのInPortの型と同じ、TimedLong を与えていることに注意 してください。

OutPortのコールバック

OutPort には、以下の2種類のコールバックが用意されています。 これらは rtm/PortCallback.h において定義されています。

OnWrite OutPort の write() が呼び出された際にコールされる OutPort::setOnWrite() 関数でセット。
OnWriteConvert OutPort の write() が呼び出された際にデータを変換するためにコールされる。OutPort::setOnWriteConvert() 関数でセット。

OnWrite コールバックは write() が呼び出された際に、OnWriteConvert は write() が呼び出された際に、ある種の変換を施したデータを送信する ために使用するコールバックです。

それぞれのコールバックは、InPort と同様 rtm/PortCallback.h で定義されて いるそれぞれのファンクタの基底クラスを継承することにより実装します。

以下にそれぞれの実装例を示します。

 #include <rtm/Portcallback.h>
 
 template <class T>
 class MyOnWrite
  : public RTC::OnWrite<T>
 {
 public:
   MyOnWrite(std::ostream& os) : m_os(os) {};
   virtual void operator()()
   {
     m_os      << "write() 関数が呼ばれました。" << std::endl;
     std::cout << "write() 関数が呼ばれました。" << std::endl;
   }
 private:
   std::ostream& m_os;
 };
 
 template <class T> 
 class MyOnWriteConvert
  : public RTC::OnWriteConvert<T>
 {
 public:
   virtual T operator()(const T& value)
   {
     T tmp;
     tmp.data = 2 * value.data;
     return tmp;
   }
 };

コールバック用のファンクタの書き方は、InPort の OnRead/OnReadConvert と ほぼ同じです。OnWrite を継承した MyOnWrite ファンクタでは、コンストラク タで出力ストリーム std::ostream を渡しています。どこかでオープンしたファ イル出力ストリーム std::ofstream 等を渡すことを意図しています。ファンク タの実体である operator()() では、出力ストリームと標準出力に対して、文 字列を出力しています。このように、ファンクタでは、予めコンストラクタな どで状態変数を渡すことで、他のオブジェクトに対する呼び出しも実現するこ とができます。

一方 OnReadConvert<T> を継承した MyOnReadConvert は operator()(const T&) のみを実装しています。この関数の引数には、read() を呼んだときに InPort 変数に読みだされる前のデータが渡されます。この関数内で何らかの処 理を行い return で返したデータは InPort 変数に書き込まれます。この例で は、データ型に data というメンバがあり、かつ乗算演算子が定義されている という前提で自乗を計算して返しています。適切なメンバがない変数型を使用 すればコンパイルエラーになります。

コネクタ・バッファのコールバック

コネクタ

コネクタはバッファおよび通信路を抽象化したオブジェクトです。図に示すよ うに、OutPort と InPort の間に存在し、OutPort からは write() 関数により データの書き込み、InPort からは read() 関数によりデータの読み出しが行わ れます。コネクタは、データがどのような手段で OutPort から InPort へ伝送 されるかを抽象化し隠蔽します。

データポートのコネクタの概念

OutPort はコネクタ内のバッファに対して、
  • 書き込み
  • 各種制御 (読み戻し、未読データへのアクセス等)
  • バッファフル状態の通知およびタイムアウトの通知 を行う(または通知を受ける)ことができます。 同様に、InPort はコネクタ内のバッファに対して
  • データの読み出し
  • 各種制御(読み戻し、未読データへのあくアクセス等)
  • バッファエンプティ状態の通知およびタイムアウト通知 を行う(または通知を受ける)ことができます。

OutPort は複数のInPortへ接続することができますが、一つの接続につき、一 つのコネクタが生成されます。(実際には InPort も複数の接続を同時に持つこ ともできますが、データを区別する方法がないので、通常は用いません。) つ まり、接続が3つあれば、コネクタが3つ存在し、それぞれに対して書き込みの ステータスが存在することになります。

また、これらの機能のために、OutPort/InPort 一対に対して、それぞれ一つコネクタが存在する必要があることがわかる。 さらに、コネクタをサブスクリプション型に対応した実装レベルでモデル化するにあたり、パブリッシャと呼ばれる非同期通信のためのオブジェクトを導入した。これを図2 に示す[1]。

データポートは接続が確立されると、1つの接続につき1つのコネクタオブジェクトを生成します。コネクタは、OutPortとInPortをつなぐデータストリームの抽象チャネルで、

ON_BUFFER_WRITE バッファ書き込み時
ON_BUFFER_FULL バッファフル時
ON_BUFFER_WRITE_TIMEOUT バッファ書き込みタイムアウト時
ON_BUFFER_OVERWRITE バッファ上書き時
ON_BUFFER_READ バッファ読み出し時
ON_SEND InProtへの送信時
ON_RECEIVED InProtへの送信完了時
ON_RECEIVER_FULL InProt側バッファフル時
ON_RECEIVER_TIMEOUT InProt側バッファタイムアウト時
ON_RECEIVER_ERROR InProt側エラー時
ON_BUFFER_EMPTY バッファが空の場合
ON_BUFFER_READTIMEOUT バッファが空でタイムアウトした場合
ON_SENDER_EMPTY OutPort側バッファが空
ON_SENDER_TIMEOUT OutPort側タイムアウト時
ON_SENDER_ERROR OutPort側エラー時
ON_CONNECT 接続確立時
ON_DISCONNECT 接続切断時

ポートのコールバック

#/bin/sh export GDK_NATIVE_WINDOWS=1 ./eclipse -vmargs -Dorg.eclipse.swt.browser.XULRunnerPath=/usr/lib/xulrunner-1.9.3.2/xulrunner

ステータス

データポートは、データの送受信を行った際に、ステータスを返します。 ステータスは、rtm/DataPortStatus.h で定義されています。

データ通信が

コネクタ
  • ConnectorProfile
  • コネクタコールバック

接続プロパティ

  • インターフェースタイプ
  • サブスクリプションタイプ
  • データフロータイプ
  • パブリッシャーポリシー
  • バッファリングポリシー

서비스포트 (응용편)

執筆中 (n-ando)

컨피규레이션 (응용편)

執筆中 (n-ando)