LEGO Mindstorm NXT RT컴포넌트

LEGO Mindstorm NXT RT컴포넌트

LEGO Mindstorm NXT(이하 NXT)는 3개의 모터와 4종류의 센서, 이것들을 제어하기 위한 인텔리전트 블록 NXT로 구성된, 로봇을 만들 수 있는 LEGO 블록입니다. NXT는 USB 또는 Bluetooth로 PC와 접속하고, 인텔리전트 블록을 프로그래밍 하거나 직접 제어 할 수 있습니다. 또, 자작의 프로그램을 인텔리전트 블록에 로드해 스탠드얼론으로 동작 시킬 수도 있습니다.

여기에서는, 인텔리전트 블록을 PC상에서 RT컴포넌트화해 다른 컴포넌트로부터 모터를 움직이거나 센서 데이터를 읽어낼 수 있도록 합니다. NXT를 1개의 RT컴포넌트화하는 것으로, 기존의 RT컴포넌트화 된 입력 디바이스로 NXT를 제어하거나 센서 데이터를 읽어내거나 하는 것이 간단하게 할 수 있게 됩니다.

Mindstorm NXT 설정

NXT를 RTC화하기 전, 먼저 NXT와 PC의 설정을 실시합니다. PC와 NXT는 USB 또는 Bluetooth로 접속할 수 있지만, NTX의 이동성을 위해 Bluetooth로 접속합니다.

블록의 조립

여기에서는, 사진에 나타내는 구성을 예를 들어, NXT를 RTC화합니다.

TribotBase.png

이것은 Tribot로 불리는 구성의 이동 베이스의 부분에 눈과 같은 초음파 센서를 전면에 달았을 뿐의 간단한 구성입니다. 이동 베이스의 조립 방법은 Mindstorm NXT의 상자에 들어 있는 「Start Here」라고 쓰여진 작은 상자안의 소책자에 자세하게 기재되어 있으므로 참조해 주십시오. 이 소책자에서 해설되고 있는 이동 베이스에 몇개의 LEGO 블록을 추가하고, 초음파 센서를 설치하면 완성입니다.

Bluetooth 디바이스의 인스톨

NXT의 인텔리전트 블록에는 기본적으로 Bluetooth가 내장되어 있습니다. 한편, PC에 Bluetooth 디바이스가 내장되어 있지 않은 경우는 사진과 같은 Bluetooth 디바이스를 PC에 설치하는 것으로, NXT와 통신할 수 있게 됩니다.

우선은, 이러한 디바이스를 설치하고 필요하다면 디바이스 드라이버를 인스톨 해 사용 준비를 합니다. Windows-XP등에서는 대부분의 시판의 Bluetooth 디바이스라면, 드라이버 인스톨 하는 일 없이 디폴트의 드라이버로 동작하는 것 같습니다.

BluetoothDevices.png

Bluetooth가 제대로 인스톨 되어 있으면, 제어판에 Bluetooth의 아이콘이 나타납니다. 이것을 클릭하면 그림과 같은 Bluetooth 설정 다이얼로그가 나타납니다.

BthDialogOption.png

「옵션」탭에서
  • 「발견 기능을 유효하게 한다」
  • 「Bluetooth 아이콘을 통지 영역에 표시한다」 를 체크합니다.

다음은 NXT와 PC를 접속하므로 이 다이얼로그는 우선 그대로 해 둡니다.

PC와 NXT의 접속

PC와 NXT를 Bluetooth로 접속하는 순서는 대체로 이하대로입니다.

  1. NXT의 전원을 넣는다
  2. NXT를 Bluetooth 검색 모드로 해 검색한다
  3. 자신의 PC를 선택
  4. 채널 선택
  5. PC의 Bluetooth 설정 다이얼로그로부터 접속 위저드를 기동
  6. 패스 키를 설정한다
  7. PC와 NXT 양자에서 접속 버튼을 누른다
  8. 접속 후 NXT를 재기동한다

NXT의 기동

NXT의 인텔리전트 블록의 중앙의 오렌지색의 버튼을 누르고 전원을 넣습니다. 전원이 켜지면 시작음과 함께 블록이 기동합니다.(음량의 설정을 0으로 하고 있는 경우에는 소리는 나오지 않습니다.) 전원을 넣으면, 그림과 같은 화면 「My files」모드가 됩니다.

NXTBoot.png

이러한 화면이 되지 않는 경우는, 오렌지색의 버튼의 밑의 네모 버튼을 몇 번인가 누르는 것으로, 「My files」모드로 할 수 있습니다.

Bluetooth 디바이스의 검색

「My files」모드 상태에서 오렌지색 버튼의 좌우에 있는, 회색의 세모 버튼을 눌러 「Bluetooth」모드에 커서를 맞추어 오렌지 버튼을 누릅니다.

NXTBluetooth.png

다시 좌우의 세모 버튼을 눌러, 「Search」모드에 커서를 맞춥니다.

NXTBthSearch.png

이 상태에서 오렌지색의 버튼을 눌러, 접속할 기기를 검색합니다. 검색 상태의 화면을 아래와 같습니다.

NXTBthSearching.png

디바이스의 접속

PC의 Bluetooth가 유효하고 NXT로부터 PC가 보이면, 그림과 같이 PC의 이름이 나타날 것입니다. 여기서 PC의 이름과는 Windows에 있어서의 「컴퓨터명」입니다.

NXTBthPCfound.png

근처에 Bluetooth 탑재 PC가 있으면, 여러개의 PC가 보일지도 모릅니다. 세모 버튼을 눌러 자신의 PC에 커서를 맞추어 오렌지색의 확인 버튼을 누릅니다.

다음에 Bluetooth의 채널 선택 화면이 되므로, 그대로 오렌지색의 확인 버튼을 누릅니다. Connecting라고 표시된 후, 패스 키 입력 화면이 되므로 그대로 오렌지 버튼을 누릅니다.

NXTBthPasskey.png

PC측에서는 그림과 같은 메세지가 표시되므로 이 메세지를 클릭합니다.

PCballoon.png

방금전 NXT에서 표시된 패스 키를 입력하고, 접속이 완료되면 그림과 같은 다이얼로그가 나타납니다. 다른 디바이스를 인식하지 않게 「발견 기능을 무효로 한다」를 체크하고 「완료」를 눌러 종료합니다.

PCConnectComp.png

접속의 확인

Bluetooth 설정 다이얼로그의 「디바이스」탭을 보면, 그림과 같이 NXT가 접속되고 있는 것을 확인할 수 있습니다.

PCDevlistNXT.png

NXT Python의 인스톨

NXT Python의 인스톨

NXT RTC를 작성하기 전에, Python로부터 Bluetooth를 사용하기 위한 모듈 PyBlues와 NXT를 제어하기 위한 모듈 NXT Python 를 인스톨 합니다.

PyBlues의 인스톨

상기 링크로부터 Windows용 인스톨러를 다운로드합니다. 다운로드한 실행 파일을 실행하면 인스톨은 완료됩니다.

NXT Python의 인스톨

상기 링크로부터 zip 파일을 다운로드합니다. NXT Python 는 setup.py 스크립트를 사용해 인스톨 합니다.
  • .py에 관련되고 있는 경우 setup.py install
  • 직접 Python 커멘드를 지정하는 경우 c:\Python24\python setup.py install

라고 커멘드 prompt에서 입력해 인스톨 합니다.


 Microsoft Windows XP [Version 5.1.2600]
 (C) Copyright 1985-2001 Microsoft Corp.
 
 C:\tmp\nxt_python-0.7>setup.py install
 running install
 running build
 running build_py
 중략
 copying build\scripts-2.4\nxt_filer -> c:\python24\Scripts
 copying build\scripts-2.4\nxt_push -> c:\python24\Scripts
 copying build\scripts-2.4\nxt_test -> c:\python24\Scripts
 
 C:\tmp\nxt_python-0.7>


NXT Python의 테스트

NXT를 ON로 해 PC에 접속되고 있는 상태로, example 이하의 샘플을 실행해 NXT Python으로 NXT를 제어할 수 있는지 확인합니다.

example 디렉토리에는 이하의 샘플이 있습니다.

  • latency.py: 센서 검출시의 지연시간을 계측한다.
  • mary.py: NXT의 사운드 기능으로 「mary had a little lamb」을 연주한다
  • message_test.py: 인텔리전트 블록의 액정화면에 메세지를 표시한다
  • spin.py: PortB, PortC의 모터를 움직여 회전시킨다
  • test_sensors.py: 전센서의 값을 표시한다

이러한 테스트를 실행할 때 , 각각의 샘플이 사용하는 모터, 센서 등이 접속된 상태에서 실행하지 않으면 안됩니다.&aname(miyamoto)

NXT Python RTC화

이상으로 PC에서 Python을 사용해 NXT를 제어할 준비가 갖추어졌습니다.

RtcTemplate등을 사용하고, NXT의 컴퍼넌트의 양식을 생성하고 코딩을 하고 싶을 단계입니다. NXT Python는 깔끔하게 정리되어 있는 Python 모듈입니다만, NXT Python의 코드를 직접 RTC의 모형에 코딩하는 것은 조금 기다려 주십시오.

샘플을 봐도 알 수 있듯이 NXT Python는 로케이터, 모터, 센서 등의 복수의 모듈로 구성되어 있고 모터나 센서에의 섬세한 기능도 제어할 수 있기 때문에 액세스 방법도 약간 번잡합니다.

여기에서는 NXT Python의 복수의 모듈을 정리해 하나의 인터페이스로 액세스하기 위한 클래스를 만들기로 하겠습니다. 이것은 소프트웨어 패턴으로 Facade 패턴입니다.

 Facade 패턴이란, GoF(Gang of Four; 4명의 갱들)에 의해서 정의된 컴퓨터 소프트웨어의 디자인 패턴의 하나이다. Facade라는 것은 「창구」를 의미한다. 관련하는 클래스군을 사용하기 위한 수속을 창구가 되는 하나의 클래스에 집약하는 것으로, 프로그램의 장황성을 없애는 것을 목적으로 한다.(출처: 프리 백과사전 「위키피디아(Wikipedia)」Facade 패턴)

이러한 클래스를 작성하면 이하의 이점이 있습니다.

  • 작성한 클래스를 RTC 이외에도 이용할 수 있다
  • 디버그가 편해진다
    • Facade 클래스 단체로 디버그 하는 것이 디버그 하기 쉽다
    • 직접 RTC화하면, 이번의 경우로 말하면, NXT Python의 사용법의 문제인가, RTC의 사용법의 문제인가 분리가 어렵다
  • 변경이 쉬워진다
    • Facade 클래스를 변경해도, 인터페이스를 변경하지 않으면 RTC측의 코드는 바꿀 필요가 없다
    • 예를 들어, get_xxx(), set_xxx()라고 하는 함수의 입출력에 리미터를 붙이고 싶은 경우, Facade 클래스측만의 변경으로 끝난다
  • NXT 이외의 디바이스에의 변경이 용이하게 된다
    • 예를 들어, NXT의 다음의 버젼의 블록이 나왔을 경우 인터페이스 만 같으면 대응이 용이하게 된다.

이것은 RTC를 작성할 때 전반적으로 말할 수 있는 것입니다. 대상 로봇이나 디바이스 등을 적절히 클래스화하는 것으로 디바이스의 변경이나, RTC 자체의 변경·버전 업에도 유연하게 대응할 수 있어 변경에 강한 소프트웨어를 만들 수 있습니다.

절대로, onExecute()등에서

ioctl(xxxx, xxxx); // UNIX 디바이스에의 직접 액세스 inb(xxx); // I/O 에의 직접 액세스 outb(xxx); // I/O 에의 직접 액세스

와 같은 프리미티브 함수 호출을 해서는 안됩니다. 이상적으로는,


 onExecute(ec_id) { // 유사 코드
    if (m_inport.isNew()) {
       retval = m_myrobot.set_actuator(m_inport.read());
       if (retval == fatal_error) return RTC::RTC_ERROR;
    }
    if (myrobot.get_sensor(sensor_data) == true) {
       m_outport.wrtie(sensor_data);
    } else {
       // read error의 경우 처리
       if (fatal_error) return RTC::RTC_ERROR;
    }
    return RTC::RTC_OK;
 }


와 같이, 「InPort에서 값을 읽어 처리」하는 함수의 호출과 「어떠한 처리에 의해 값을 취득해 OutPort로 출력」하는 함수의 호출만으로부터 되는, 「추상 레벨이 높은」코드로 해야 합니다.

NXT Python Facade 클래스

NXT Python Facade 클래스

NXT Python 자체는 NXT 인텔리전트 블록의 거의 모든 기능을 사용할 수 있습니다만, 그러한 기능 모든 것을 직접 사용하는 것은 번잡하기 때문에 사용하고 싶은 기능만을 인터페이스 하는 Facade 클래스를 만듭니다.

NXT의 주된 기능을 열거합니다.

  • 입력
    • 모터 속도값을 준다
    • 사운드의 음정값을 준다
    • 액정 표시에 출력하는 메세지를 준다
  • 출력
    • 모터 엔코더값을 읽는다
    • 센서값(사운드, 초음파, 터치, 빛)을 읽는다
    • 시스템 정보를 읽는다
  • 그 외
    • NXT의 검색
    • NXT에의 접속
    • NXT의 파일의 읽고 쓰기
    • 펌웨어(firmware)의 읽고 쓰기

이것들 모든 것을 한 번에 서포트하는 클래스를 만드는 것은 Facade 클래스를 만드는 의미가 없어져 버립니다. 필요한 기능이 나오면 그때 그때 Facade 클래스를 갱신하면 좋습니다. 따라서, 여기서 작성하는 Facade 클래스에서는 이번에 작성하는 로봇에 맞추어 이하의 기능만을 지원하기로 하겠습니다.

  • 입력
    • 모터 속도치값을 준다: setMotors()
  • 출력
    • 모터 엔코더값을 읽는다: getMotors()
    • 센서값(사운드, 초음파, 터치, 빛)을 읽는다: getSensors()

함수명은 대체로 이미지입니다. 이 생각으로 작성한 클래스(NXTBrick.py)를 이하에 나타냅니다.


 #!/usr/bin/env python
 # @file NXTBrick.py
 # -*- coding:shift_jis -*-
 
 import nxt.locator
 from nxt.sensor import *
 from nxt.motor import *
 
 class NXTBrick:
     def __init__(self, bsock=None):
         """
         생성자   
         NXT 블록에의 연결을 실시해 모터나 센서 오브젝트를
         작성한다. 모터의 엔코더의 리셋을 실시한다.
         """
         if bsock:
             self.sock = bsock
         else:
             self.sock = nxt.locator.find_one_brick().connect()
 
         self.motors = [Motor(self.sock, PORT_A),
                        Motor(self.sock, PORT_B),
                        Motor(self.sock, PORT_C)]
             
         self.sensors = [TouchSensor(self.sock, PORT_1),
                         SoundSensor(self.sock, PORT_2),
                         LightSensor(self.sock, PORT_3),
                         UltrasonicSensor(self.sock, PORT_4)]
         self.resetPosition()
 
     def close(self):
         """
         NXT 블록과의 접속을 종료한다
         """
         self.sock.close()
 
     def resetPosition(self, relative = 0):
         """
         NXT의 모터의 엔코더를 리셋한다
         """
         for m in self.motors:
             m.reset_position(relative)
 
     def setMotors(self, vels):
         """
         배열을 받아, 모터의 파워로서 세트 한다.
         vels의 수와 모터의 수가 일치하지 않는 경우, 양자의 요소수 중
         작은 분으로 루프를 돌린다.
         """
         for i, v in enumerate(vels[:min(len(vels),len(self.motors))]):
             self.motors[i].power = max(min(v,127),-127)
             self.motors[i].mode = MODE_MOTOR_ON | MODE_REGULATED
             self.motors[i].regulation_mode = REGULATION_MOTOR_SYNC
             self.motors[i].run_state = RUN_STATE_RUNNING
             self.motors[i].tacho_limit = 0
             self.motors[i].set_output_state()
 
     def getMotors(self):
         """
         모터의 위치(각도)를 취득한다
         
         """
         state = []
         for m in self.motors:
             state.append(m.get_output_state())
         return state
 
     def getSensors(self):
         """
         센서의 값을 취득한다. 받았던 데이터는 배열로 반환한다.
         """
         state = []
         for s in self.sensors:
             state.append(s.get_sample())
         return state
 
 
 """
 테스트 프로그램
 모터에 적당한 출력을 주고 각도를 읽어 표시한다.
 센서로부터 값을 읽어들여 표시한다.
 """
 if __name__ == "__main__":
     import time
     nxt = NXTBrick()
     print "connected"
     
     # 모터의 테스트
     for i in range(100):
         nxt.setMotors([80,-80,80])
         print "Motor: "
         mstat = nxt.getMotors()
         for i, m in enumerate(mstat):
             print "(" , i, "): ", m
         time.sleep(0.1)
     nxt.setMotors([0,0,0])
 
     # 센서의 테스트
     for i in range(100):
         sensors = ["Touch", "Sound", "Light", "USonic"]
         sval = nxt.getSensors()
         for s in sensors:
             print s + ": " + sval
             print ""
             time.speel(0.1)


마지막 if name == "main":로부터 시작되는 부분은 테스트 프로그램입니다. 이 모듈을 단체(単体)로 움직였을 때에 실행됩니다. 우선은, 이 모듈이 완전하게 움직일 때까지 테스트를 하는 것이 중요합니다.

이상으로, NXT의 Facade 클래스가 생겼습니다. 매우 간단한 클래스입니다만, 모터에 속도를 주고 포지션을 읽어 센서의 값을 읽을 수 있는 클래스가 생겼습니다. 처음터 뭐든지 할 수 있도록 하려고 하면, 결국 무엇을 하기 위한 클래스인가 잘 모르는 클래스가 생기게 됩니다. 버전 업은 언제라도 할 수 있으므로, 우선은 최저한의 기능에서도 제대로 움직이는 클래스를 만드는 것이 중요합니다.

NXT RTC의 구현

NXT RTC의 구현

그럼, 앞에서 구현한 NXTBrick 클래스를 만들기 위한 RTC를 작성합니다. 작성하는 RTC의 사양은 이하대로입니다.

  • InPort
    • 모터 속도 (TimedFloatSeq)
  • Outport
    • 모터 위치 (TimedFloatSeq)
    • 센서 데이터 (TimedFloatSeq)

NXTRTC의 모형을 생성

NXTRTC의 모형을 생성

RtcTemplate로 모형을 작성합니다. 모형을 작성하는 방법에는, 커멘드 라인판의 rtc-template를 사용하는 방법과 Eclipse판의 RtcTemplate를 사용하는 방법이 있습니다.

rtc-template를 실행하기 위해서, 이하와 같은 배치파일을 작성합니다.


 python "C:\Program Files\OpenRTM-aist\0.4\utils\rtc-template\rtc-template.py" -bpython^
  --module-name=NXTRTC --module-desc="NXT sample component"^
  --module-version=0.1 --module-vendor=AIST --module-category=example^
  --module-comp-type=DataFlowComponent --module-act-type=SPORADIC^
  --module-max-inst=10^
  --inport=vel:TimedFloatSeq^
  --outport=pos:TimedFloatSeq --outport=sens:TimedFloatSeq^
  --config="map:string:A,B"


Eclipse판 RtcTemplate에서는 아래와 같이 됩니다.
  • Programing language selection: Python
  • Module definition
    • Module name: NXTRTC
    • Module decription: NXT sample component
    • Module version: 0.1
    • Module vender: AIST
    • Module category: example
    • Component type: DataFlowComponent
    • Component's activity type: SPORADIC
    • Number of maximum instance: 10
  • InPort definition
    • Ports: Name:vel Type:TimedFloatSeq
  • OutPort definition
    • Ports: Name:pos, Type:TimedFloatSeq
    • Ports: Name:sens, Type:TimedFloatSeq
  • ConfigurationSet definition
    • Cfg Sets: Name:map, Type:string, Default Value: A,B

rtc-template(gen.bat)의 실행


 > gen.bat
  python "C:\Program Files\OpenRTM-aist\0.4\utils\rtc-template\rtc-template.py"
  -bpython --module-name=NXTRTC --module-desc="NXT sample component" 
  --module-version=0.1 --module-vendor=AIST --module-category=example 
  --module-comp-type=DataFlowComponent --module-act-type=SPORADIC 
  --module-max-inst=10 --inport=vel:TimedFloatSeq 
  --outport=pos:TimedFloatSeq --outport=sens:TimedFloatSeq
  --config="map:string:A,B"
 
   File "NXTRTC.py" was generated.
   File "README.NXTRTC" was generated.
   File "NXTRTC.yaml" was generated.


이상과 같이, NXTRTC.py등의 모형 파일이 생겼습니다.

샘플 코드 설명

샘플 코드의 설명

여기로부터, NXTRTC.py 에 방금전 작성한 NXTBrick.py의 기능을 작성해 갑니다.

  • NXTBrick.py의 import NXTBrick 클래스를 사용하기 위해서 NXBrick.py를 import 합니다. import의 기술 방법은, 아래와 같이 확장자(extension)(.py)를 제외한 파일명을 지정합니다.
     import NXTBrick
  • onInitialize(self)의 구현 onInitialize()에서 NXTBrick 클래스의 인스턴스화를 실시합니다. NXTBrick 클래스의 인스턴스화 과정에서 에러가 발생했을 경우, RTC_ERROR가 돌아와 종료 상태로 천이 합니다.
            # create NXTBrick object
            try:
                self._nxtbrick = NXTBrick.NXTBrick()
            except:
                print "NXTBrick create failed."""
                return RTC.RTC_ERROR
  • onActivated(self, ec_id), onDeactivated(self, ec_id)의 구현 onActivated(), onDeactivated()에서는 NXTBrick 클래스의 resetPosition() 메소드를 부릅니다.
            self._nxtbrick.resetPosition() 
  • onExecute(self, ec_id)의 구현 onExecute()에서는 아래와 같은 처리를 실시하고 있습니다.
    • 데이터 InPort로부터의 속도의 읽기.
    • Configuration 기능을 이용해 NXT의 어느 포트에 속도 출력을 실시하는지를 결정.
    • NXTBrick의 setMotors() 메소드에서 모터에 속도를 출력.
    • NXTBrick의 getSensors()에서 NTX의 초음파 센서값을 취득해, 데이터가 있으면 OutPort으로 출력.
    • NXTBrick의 getMotors()에서 NTX의 회전 각도[deg]를 취득해, 데이터가 있으면 OutPort으로 출력.

상기를 정리한 샘플 코드는 아래와 같습니다.


 #!/usr/bin/env python
 # -*- coding:shift_jis -*-
 # -*- Python -*-
 
 import sys
 import time
 sys.path.append(".")
 
 # Import RTM module
 import OpenRTM
 import RTC
 
 
 # import NXTBrick class
 import NXTBrick
 
 
 # This module's spesification
 # <rtc-template block="module_spec">
 nxtrtc_spec = ["implementation_id", "NXTRTC", 
          "type_name",         "NXTRTC", 
          "description",       "NXT sample component", 
          "version",           "0.1", 
          "vendor",            "AIST", 
          "category",          "example", 
          "activity_type",     "DataFlowComponent", 
          "max_instance",      "10", 
          "language",          "Python", 
          "lang_type",         "SCRIPT",
          "conf.default.map", "A,B",
          ""]
 
 # </rtc-template>
 
 class NXTRTC(OpenRTM.DataFlowComponentBase):
     def __init__(self, manager):
         OpenRTM.DataFlowComponentBase.__init__(self, manager)
 
         # DataPorts initialization
         # <rtc-template block="data_ports">
         self._d_vel = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._velIn = OpenRTM.InPort("vel", self._d_vel, OpenRTM.RingBuffer(8))
         self.registerInPort("vel",self._velIn)
         self._d_pos = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._posOut = OpenRTM.OutPort("pos", self._d_pos, OpenRTM.RingBuffer(8))
         self.registerOutPort("pos",self._posOut)
         self._d_sens = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._sensOut = OpenRTM.OutPort("sens", self._d_sens, OpenRTM.RingBuffer(8))
         self.registerOutPort("sens",self._sensOut)
 
         # initialize of configuration-data.
         # <rtc-template block="configurations">
         self._map = [['A', 'B']]
         self._nxtbrick = None
         self._mapping = {'A':0,'B':1,'C':2}
          
     def onInitialize(self):
         # Bind variables and configuration variable
         # <rtc-template block="bind_config">
         self.bindParameter("map", self._map, "A,B")
 
         # create NXTBrick object
         try:
             print "Connecting to NXT brick ...."
             self._nxtbrick = NXTBrick.NXTBrick()
             print "Connection established."
         except:
             print "NXTBrick connection failed."
             return RTC.RTC_ERROR
 
         return RTC.RTC_OK
 
     def onFinalize(self):
         self._nxtbrick.close()
 
 
     def onActivated(self, ec_id):
         # reset NXTBrick's position.
         self._nxtbrick.resetPosition()
 
         return RTC.RTC_OK
 
     def onDeactivated(self, ec_id):
         # reset NXTBrick's position.
         self._nxtbrick.resetPosition()
 
         return RTC.RTC_OK
 
     def onExecute(self, ec_id):
         cnt = 0
         # check new data.
         if self._velIn.isNew():
             # read velocity data from inport.
             self._d_vel = self._velIn.read()
             vel_ = [0,0,0]
             vel_[self._mapping[self._map[0][0]]] = self._d_vel.data[0]
             vel_[self._mapping[self._map[0][1]]] = self._d_vel.data[1]
             # set velocity
             self._nxtbrick.setMotors(vel_)
 
         # get sensor data.
         sensor_   = self._nxtbrick.getSensors()
         if sensor_:
             self._d_sens.data = sensor_
             self._sensOut.write()
 
         # get position data.
         position_ = self._nxtbrick.getMotors()
         if position_:
             self._d_pos.data = \
                 [position_[self._mapping[self._map[0][0]]][9], \
                      position_[self._mapping[self._map[0][1]]][9]]
         # write position data to outport.
         self._posOut.write()
 
         return RTC.RTC_OK
 
 
 
 def MyModuleInit(manager):
     profile = OpenRTM.Properties(defaults_str=nxtrtc_spec)
     manager.registerFactory(profile,
                             NXTRTC,
                             OpenRTM.Delete)
 
     # Create a component
     comp = manager.createComponent("NXTRTC")
 
 
 
 def main():
     mgr = OpenRTM.Manager.init(len(sys.argv), sys.argv)
     #mgr = OpenRTM.Manager.init(sys.argv)
     mgr.setModuleInitProc(MyModuleInit)
     mgr.activateManager()
     mgr.runManager()
 
 if __name__ == "__main__":
     main()


샘플 코드의 설명(콜백 오브젝트의 사용 예)

이 샘플에서는 상기의 샘플에 콜백 OnWrite를 추가해, InPort의 버퍼에 데이터가 써졌을 때에 모터에의 속도를 출력하도록 확장합니다.

  • 콜백 클래스
    아래와 같이 콜백 클래스를 기술합니다.


 # @class CallBackClass
 # @brief callback class
 #
 # when data is written in the buffer of InPort,
 # it is called.
 class CallBackClass:
     def __init__(self, nxtbrick_, map_):
         self._nxtbrick = nxtbrick_
         self._map = map_
         self._mapping = {'A':0,'B':1,'C':2}
 
     def __call__(self, pData):
         vel_ = [0,0,0]
         vel_[self._mapping[self._map[0][0]]] = pData.data[0]
         vel_[self._mapping[self._map[0][1]]] = pData.data[1]
         # set velocity
         self._nxtbrick.setMotors(vel_)


이 클래스에서는 NTXBrick 클래스의 오브젝트와 컨피그레이션 변수 map를 인수로 합니다.

  • 콜백 클래스의 등록
    아래와 같이, setOnWrite() 메소드에서 콜백 오브젝트를 등록합니다.

   # set callback class
   self._velIn.setOnWrite(CallBackClass(self._ntxbrick,self._map))

setOnWrite()에 의해, InPort에 데이터가 써졌을 때에, CallBackClass의 call() 메소드가 불리게 됩니다.

위의 내용을 정리한 샘플 코드는 아래와 같습니다.


 #!/usr/bin/env python
 # -*- coding:shift_jis -*-
 # -*- Python -*-
 
 import sys
 import time
 sys.path.append(".")
 
 # Import RTM module
 import OpenRTM
 import RTC
 
 
 # import NXTBrick class
 import NXTBrick
 
 
 # This module's spesification
 # <rtc-template block="module_spec">
 nxtrtc_spec = ["implementation_id", "NXTRTC", 
          "type_name",         "NXTRTC", 
          "description",       "NXT sample component", 
          "version",           "0.1", 
          "vendor",            "AIST", 
          "category",          "example", 
          "activity_type",     "DataFlowComponent", 
          "max_instance",      "10", 
          "language",          "Python", 
          "lang_type",         "SCRIPT",
          "conf.default.map", "A,B",
          ""]
 
 # </rtc-template>
 
 # @class CallBackClass
 # @brief callback class
 #
 # when data is written in the buffer of InPort,
 # it is called.
 class CallBackClass:
     def __init__(self, nxtbrick_, map_):
         self._nxtbrick = nxtbrick_
         self._map = map_
         self._mapping = {'A':0,'B':1,'C':2}
 
     def __call__(self, pData):
         vel_ = [0,0,0]
         vel_[self._mapping[self._map[0][0]]] = pData.data[0]
         vel_[self._mapping[self._map[0][1]]] = pData.data[1]
         # set velocity
         self._nxtbrick.setMotors(vel_)
 
 
 class NXTRTC(OpenRTM.DataFlowComponentBase):
     def __init__(self, manager):
         OpenRTM.DataFlowComponentBase.__init__(self, manager)
 
         # DataPorts initialization
         # <rtc-template block="data_ports">
         self._d_vel = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._velIn = OpenRTM.InPort("vel", self._d_vel, OpenRTM.RingBuffer(8))
         self.registerInPort("vel",self._velIn)
         self._d_pos = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._posOut = OpenRTM.OutPort("pos", self._d_pos, OpenRTM.RingBuffer(8))
         self.registerOutPort("pos",self._posOut)
         self._d_sens = RTC.TimedFloatSeq(RTC.Time(0,0),[])
         self._sensOut = OpenRTM.OutPort("sens", self._d_sens, OpenRTM.RingBuffer(8))
         self.registerOutPort("sens",self._sensOut)
 
         # initialize of configuration-data.
         # <rtc-template block="configurations">
         self._map = [['A', 'B']]
         self._nxtbrick = None
         self._mapping = {'A':0,'B':1,'C':2}
          
     def onInitialize(self):
         # Bind variables and configuration variable
         # <rtc-template block="bind_config">
         self.bindParameter("map", self._map, "A,B")
 
         # create NXTBrick object
         try:
             print "Connecting to NXT brick ...."
             self._nxtbrick = NXTBrick.NXTBrick()
             print "Connection established."
         except:
             print "NXTBrick connection failed."
             return RTC.RTC_ERROR
 
         # set callback class
         self._velIn.setOnWrite(CallBackClass(self._ntxbrick,self._map))
 
         return RTC.RTC_OK
 
     def onFinalize(self):
         self._nxtbrick.close()
 
     def onActivated(self, ec_id):
         # reset NXTBrick's position.
         self._nxtbrick.resetPosition()
 
         return RTC.RTC_OK
 
 
     def onDeactivated(self, ec_id):
         # reset NXTBrick's position.
         self._nxtbrick.resetPosition()
 
         return RTC.RTC_OK
 
 
     def onExecute(self, ec_id):
         # get sensor data.
         sensor_   = self._nxtbrick.getSensors()
         if sensor_:
             self._d_sens.data = [sensor_[3]]
             # write sensor data to outport.
             self._sensOut.write()
 
         # get position data.
         position_ = self._nxtbrick.getMotors()
         if position_:
             self._d_pos.data = [position_[self._mapping[self._map[0][0]]][9],position_[self._mapping[self._map[0][1]]][9]]
             # write position data to outport.
             self._posOut.write()
 
         return RTC.RTC_OK
 
 
 
 def MyModuleInit(manager):
     profile = OpenRTM.Properties(defaults_str=nxtrtc_spec)
     manager.registerFactory(profile,
                             NXTRTC,
                             OpenRTM.Delete)
 
     # Create a component
     comp = manager.createComponent("NXTRTC")
 
 
 
 def main():
     mgr = OpenRTM.Manager.init(len(sys.argv), sys.argv)
     #mgr = OpenRTM.Manager.init(sys.argv)
     mgr.setModuleInitProc(MyModuleInit)
     mgr.activateManager()
     mgr.runManager()
 
 if __name__ == "__main__":
     main()


처음의 예에서는 모터에의 출력, 센서값의 읽기, 모터 엔코더의 읽기를 모두 동일 루프로 동기적으로 하고 있었습니다만, 콜백을 사용하는 것으로 데이터가 오자 마자 모터에 값을 출력할 수 있게 됩니다.

NXT RTC 기동확인

NXT RTC 동작 확인

그럼, 작성한 NXTRTC를 실제로 동작시켜 보겠습니다.

네임서버의 기동

네임서버를 기동합니다. Windows용의 OpenRTM-aist(C++버전)이 인스톨 되어 있는 경우는, 스타트 메뉴의 「OpenRTM-aist」→「C++」→「examples」→「Start Naming Service」로부터 기동할 수 있습니다.

rtc.conf의 작성

rtc.conf를 작성합니다.

 corba.nameservers: localhost
 naming.formats: %n.rtc
 manager.shutdown_auto: NO

corna.namingservers의 항목은 사용하고 싶은 네임서버의 주소에 맞게 설정합니다. 여기에서는, 로컬의 네임서버를 사용하기 때문에 localhost로 합니다.

기동하고 싶은 컴포넌트가 들어가 있는 각각의 디렉토리에 복사해 둡니다. 이번 작성한 NXTRTC 외에
  • TkJoystickComp
  • TkMotorPosComp
  • TkSliderMonitorComp 를 기동하기로 하겠습니다.

RTSystemEditor의 기동

RTSystemEditor를 기동합니다. RTSystemEditor를 기동 후, 네임서버(여기에서는 localhost)에 접속합니다. 또, SystemDiagram 에디터를 열기 위해서, 툴바의 SystemDiagram 아이콘을 클릭합니다.

컴포넌트의 기동

이하의 컴포넌트를 기동합니다.
  • NXTRTC
  • TkJoystickComp
  • TkMotorPosComp
  • TkSliderMonitorComp 기동하면, RTSystemEditor의 NameServcie 뷰상에 컴포넌트가 나타납니다.

TkJoystickComp

TkJouyStickComp는 GUI상에서 조이스틱을 모의하기 위한 컴포넌트입니다. 그림에 나타내는 GUI가 나타나 중앙의 원을 드래그 하는 것으로 조이스틱과 같이, X-Y의 값을 OutPort(위)로부터 출력하는 컴포넌트입니다. TkJoyStickComp에는 또 하나 OutPort(아래)가 있고, 이쪽은 이동 로봇을 조작하는데 편리한 좌우의 차바퀴의 속도를 출력하기 위한 데이터 포트입니다. NXTRTC의 입력 포트에 직접 접속하면, Tribot를 제어할 수 있습니다.

TkJoystick.png

TkMotorComp

TkMotorComp는 GUI상에서 차바퀴의 모형인 원이 InPort에 입력된 데이터(각도)에 따라서 회전하는, 차바퀴의 회전의 모습을 모니터링하기 위한 컴포넌트입니다. NXTRTC의 차바퀴의 각도를 출력하는 OutPort에 접속하는 것으로 NXT의 차바퀴의 회전의 상태를 볼 수 있습니다. NXT의 차바퀴를 손으로 돌려도 그 모습을 모니터 할 수 있습니다.

TkMotor.png

TkSliderMonitorComp

TkSliderMonitorComp는 InPort에 입력된 데이터의 값을 GUI의 슬라이더로 표시하기 위한 컴포넌트입니다. NXTRTC의 센서 출력 포트에 접속하는 것으로 NXT의 센서 데이터의 모니터링을 할 수 있습니다.

컴포넌트의 접속

모든 컴포넌트가 기동되면 컴포넌트를 접속합니다. NameSercie 뷰로부터 SystemDiagram 에디터에 각 컴포넌트를 드래그 앤드 드롭 합니다. 접속하고 싶은 포트로부터 포트에 드래그 앤드 드롭 해서 포트를 연결합니다. 아래에, 몇개의 컴포넌트를 접속한 예를 나타냅니다.

RtcLink_ko.png

이러한 컴포넌트를 사용해 NXTRTC가 올바르게 움직이고 있는지를 확인합니다.