Mote 애플리케이션개발 "Development of Mote"
Ⅰ NesC 목차 1. Introduction 1.1 개략적설명 1 1.2 NesC의탄생배경 1 1.3 응용분야 1 2. Interfaces 2.1 개념 1 2.2 Interface instance 1 2.3 command와 event 2 2.4 Interface type 2 2.5 Interface의양방향성 2 2.6 Interface function 2 3. Component 3.1 Configuration 3 3.2 Module 5 4. Concurrency in nesc 4.1 Task 5 4.2 Event 8 4.3 Race condition 8 4.4 Atomic statements 12 5. Appendix 5.1 nesc Compiler 14 5.2 nesc에서사용하는용어 15 6. Component 분석 6.1 Micaz ADC 16 6.2 Timer 23 6.3 GENERICCOMM 36 6.4 Micaz Surge Routing 45
6.5 Event Library 64 6.6 MultihopRouter & MultihopEngineM Component 83 Ⅱ TinyOS Operation 1. TinyOS 란? 1.1 디렉토리구조 93 1.2 TinyOS Layers 94 1.3 TinyOS Component Hierarchy 95 2. TinyOS 설치하기 2.1 TinyOS 다운받기 95 2.2 설치과정 96 2.3 What's Being Installed? 97 3. TinyOS Application 시작하기 3.1 application 설치하기 98 3.2 Blink Application 99 3.3 CntToLedsAndRfm & RfmToLeds 100 3.4 OscilloscopeRF 100 3.5 PC Application 101 3.6 Surge 104
Ⅰ. NesC 1. Introduction 1.1. 개략적설명 nesc는 TinyOS의프로그래밍언어로event-based execution model의 component 기반언어이다. nesc의 component는 state를내부에가지고있고잘정의된 interface를통해 interact 한다는점에서 object와유사하다. 그러나 component들과 component 사이의 interaction이컴파일시점에정해진다는점에서큰차이가있다. 1.2. NesC의탄생배경 nesc는 network embedded system C의약자로 component 기반 C dialect이다. nesc는 TinyOS의 structuring concepts과 execution model을구체화하기위해고안된 C programming language의확장판으로 NES-see' 라발음된다. nesc의 component는 state를캡슐화하고, functionality를갖는 state를연결한다는점에서 object와유사하다. nesc의가장큰차이는 naming scope에있다. Global namespace에서함수나변수를참조하는 C++ 이나 Java의 object와는달리 nesc의 component는 local namespace 만을이용한다. 즉 component는 component를구현하는함수를선언하는것과더불어 component가 call하는함수를선언해야만한다는것을말한다. 1.3. 응용분야 nesc는구조적개념과 TinyOS 실행모델을구체화하기위해디자인된 C의확장입니다. ToinyOS는제한된자원을가진 sensor network node를위해디자인된 event-driven operating system입니다. 2. Interfaces 2.1. 개념 NesC에서 interface는양방향성을가지고제공자 (provider) 와사용자 (user) 가되는컴포넌트를연결하는포트의역할을수행한다. interface는 command와 event 타입의함수로정의된다. 2.2. Interface instance component specification에서특정한 interface type이다. instance name, 역할 (provider나 user), interface type 그리고선택적으로 - 1 -
interface parameter를가진다. parameter가없는 interface instance 는 simple interface instance이고, parameter가있는 interface instance는 parameterised interface instance이다. 2.3. command와 event 2.3.1. interface의선언형 command와 event는 interface의선언형으로다음표에서간단히설명하고있다. command : command 로정의된함수는현컴포넌트의 module 부분에구현된함수로서, 현컴포넌트를사용하는상위컴포넌트에서 call' 명령을통해호출된다. event : event로정의된함수는현컴포넌트를사용하는상위컴포넌트에구현되어야하는함수로서, 특정인터럽트나조건이만족되었을경우, 현컴포넌트가어떤정보를상위컴포넌트에게전달할때사용한다. 그림 Ⅰ-2-1 event와 command 타입인터페이스 2.4. Interface type 두 component(provider 와 user) 사이에상호작용을특성화한다. 이러한 specification은 command와 event의집합을형성한다. 각각의 interface type은구분된이름을가진다. 2.5. Interface의양방향성 interface의 provider는 command를수행하고 interface user는 event를수행한다. 2.6. Interface function 전체 application의 wiring 구문에의해특성화됨으로서, component의 - 2 -
command와 event의행동을나타내는선언만존재하는함수 3. Component TinyOS에서는앞에서설명한인터페이스를통해다른컴포넌트들간의연결및서로간의함수호출이가능하다. 하나의컴포넌트는자신이사용할하위컴포넌트들을선언하고, 그들간의연결을정의하는 configuration 파일과자신의구현내용을기술하고있는 module 파일로이루어진다. 일반적으로 < 컴포넌트이름 >.nc 또는 < 컴포넌트이름 >M.nc 같은이름은 module을위해사용되는파일이름이다. 3.1. Configuration configuration은다른컴포넌트와의연결에대한내용을정의하고있는데, 연결에사용할컴포넌트를나열하고그들간의연결을기술하는방법은다음과같다. configuration C { provides interface X; implementation { components C1, C2; X = C1.X; X = C2.X; C는선언된컴포넌트이름을의미하며, 그안에는현컴포넌트에서제공하는인터페이스 (X) 가선언되어있다. 그리고그밑에기술된 implementation 부분에는현컴포넌트에서사용할하부컴포넌트들 (C1, C2) 이차례대로선언된다. 그아래의문장들은컴포넌트들간의연결을기술하고있다. 컴포넌트들사이의연결을 wiring이라고한다. nesc는관계가있는함수들을모으기위하여 "interfaces" 라는것이있다. Component의 "specifications" 는거의항상 "interfaces" 의 term 으로되어있다. 예를들면 power 의 management 및 configration 문제에서, application은종종센서값을읽기위하여센서를켜거나 packet을읽어들이기위하여 radio stack을켜기위하여 system abstraction과 service를 "start" 및 "stop' 할수있어야할필요가있다. "StdControl" interface는이러한것을표현하는보편적인방법으로다음과같이 interface를나타낼수있다. interface StdControl { command error_t start(); command error_t stop(); - 3 -
켜거나끌수있는 abstraction 또는 service를표현하는 component 는 "StdControl" 을 provides하고다른것을켜거나끌필요가있는 component "StdControl" 을 "uses" 한다. 이것은대체로계층적관계를갖는다. 예를들면 routing layer는 data link layer를 start하고 idel channel을감지하여 start 및 stop할필요가있다. module RoutingLayerC { provides interface StdControl; uses interface StdControl as SubControl; module PacketLayerC { provides interface StdControl; provider와 user를함께연결하는것을 "wiring" 이라고부른다. 예를들면 RoutingLayerC의코드는 SubControl.start() 및 SubControl.stop() 을 call하는함수를갖는다. SubControl이 provider에 "wiring" 되지않는다면이함수는 undefined symbols이며, 어떤실제의코드와도관계가없다. 그러나 SubControl이 PacketLayerC의 StdControl에 wiring 된다면 RoutingLayerC가 SubControl.start() 를호출할때 PacketLayerC 의 StdControl.start() 를 invoke한다. 이것은 RoutingLayerC.SubControl.start 의참조는 PacketLayerC.StdControl.start의정의를가리킴을의미한다. 두 component RoutingLayerC 및 PacketLayerC는완전히 decouple되어있고 wiring될때만 bound 된다. 3.1.1. 와이어링의종류와이어링은 ->, <-, = 세가지의기호로나타낸다. interface1=interface2 2개의 interface가같음을의미하며양쪽 interface가서로같은제공자이거나사용자인경우와한쪽은제공자이고다른쪽은사용자인경우를의미한다. interface1->interface2 interface의구성함수가링크되어있음을의미한다. 즉, interface1 에서사용한함수가 interface2에구현되어있음을나타낸다. interface1<-interface2 interface2->interface1과동일한의미이다. - 4 -
3.2. Module 3.2.1. Implementing the Module s Specification module은 C 코드로된 component 특성화작업을수행한다. module-implementation implementation { translation-unit transltion-unit은 C 선언과정의에관한목록이다. transltion-unit은반드시모듈상의제공된모든 command(event) 를수행해야한다.( 직접적으로제공된모든 command와 event, 제공된 interface상의모든 command와사용된 interface상에서의모든 event) module은그것의 command를호출할수있고그것의 event에대한신호를보낼수있다. 3.2.2. Calling Commands and Signaling Events 단일 command는 > call command-name(...) 에의해호출되어지고, 단일 event는 > signal event-name 에의해신호가보내어진다. 예를들어 SendMsg 타입의 interface send를사용하는 module에서는 > call Send.send(1, sizeof(message), &msg1) 로표현된다. 4. Concurrency in nesc 4.1. Task nesc에서는 command에서 event를 signal하는것은좋지않은방법에해당한다. 왜냐하면그러한 implementation은 call loop을발생할수있기때문이다. 다음의코드를살펴보자. module FilterMagC { provides interface StdControl; provides interface Read<uint16_t>; uses interface Timer<TMilli>; uses interface Read<uint16_t> as RawRead; implementation {... module PeriodicReaderC { provides interface StdControl; uses interface Timer<TMilli>; uses interface Read<uint16_t>; - 5 -
implementation { uint16_t filterval = 0; uint16_t lastval = 0; command error_t StdControl.start() { return call Timer.startPeriodic(10); command error_t StdControl.stop() { return call Timer.stop(); event void Timer.fired() { call Read.read(); event void Read.readDone(error_t err, uint16_t val) { if (err == SUCCESS) { lastval = val;filterval *= 9; filterval /= 10; filterval += lastval / 10; command error_t Read.read() { signal Read.readDone(SUCCESS, filterval); //command 내부에서 event Read.readDone() 을 signal 함 이와같이 command 내부에서의 event의 signaling은 stack과관련하여심각한문제를발생시킬수있다. 예를들어 FasSamplerC가 high sampling rate로많은횟수의센서값을 sample하려고할때다음과같이 implementation을하게된다. event void Read.readDone(error_t err, uint16_t val) { buffer[index] = val;index++; if (index < BUFFER_SIZE) { call Read.read(); 이러한경우 read와 readdone 사이에긴 call lop가형성될수있다. complier가이러한것을 optimize할수없을때에는 stack이증가하게된다. 메모리가큰 PC같은경우에는그다지중요하지않을수있지만, MSP430과같이극히제한된 RAM의양과하드웨어 emory protection이없는경우이와같은 stack의증가는 data memory를훼손하고프로그램이깨어지는결과를초래할수있다. 따라서 command 내부에서 event를 signal하는것은좋은프로그래밍방법이아니다. 이러한문제를피하기위하여 deffered procedure call 인 task 를이용 - 6 -
한다. Module은 task를 scheduler에게 post할수있다. 그러면어떤시간이지난시점에서 scheduler는 task를수행하게된다. task가즉시 call되지않기때문에 return 값은없다. 그리고 task는 component 의 naming scope안에서수행되기때문에어떤 parameter도취하지않는다. function과같이 task는미리선언할수있으며그형식은다음과같다. task void readdonetask(); Component는다음과같은 keyword로 scheduler에게 task를 post한다. post readdonetask(); stack의문제가발생하지않도록고치면처음의예제코드를다음과같이표현할수있다. module FilterMagC { provides interface StdControl; provides interface Read<uint16_t>; uses interface Timer<TMilli>; uses interface Read<uint16_t> as RawRead; module PeriodicReaderC { provides interface StdControl; uses interface Timer<TMilli>; uses interface Read<uint16_t>; implementation { uint16_t filterval = 0; uint16_t lastval = 0; task void readdonetask(); command error_t StdControl.start() { return call Timer.startPeriodic(10); command error_t StdControl.stop() { return call Timer.stop(); event void Timer.fired() { call RawRead.read(); event void RawRead.readDone(error_t err, uint16_t val) { if (err == SUCCESS) { lastval = val; filterval *= 9; filterval /= 10; filterval += lastval / 10; - 7 -
command error_t Read.read() { post readdonetask(); return SUCCESS; task void readdonetask() { signal Read.readDone(SUCCESS, filterval); 4.2. Event TinyOS에서는실행된모든 task가끝난후, 큐가비어있을경우에는다른 task가생성되기전까지 CPU의전원을최소화하여 CPU의소모에너지를낮춘다. task는다른 task에의해선점되지않지만, event에의해서는선점된다. event는특정하드웨어인터럽트나특정조건을만족했을경우호출되는프로세스로서다른 task보다먼저실행되는특징이있다. 일반적으로인터럽트에의해어떤 event가발생되면연결된컴포넌트들에따라연속적으로상위컴포넌트들의특정함수가호출되며, 동시에그에따른여러처리함수들이 task 형태로만들어져 FIFO 큐에저장된다. 4.3. Race condition Task는 non-preemptive이기때문에어떤시점에서오직하나의 task 만이수행된다. 즉다른 task가수행되고있는중에는이 task를 preemption하고서다른 task를수행할수없다는것이고따라서수행중인 task가종료될때까지다른 task들은모두기다려야만한다. 이모든것은 scheduler가알아서하기때문에 user는신경을쓸필요가없다. 즉 task가수행중인데다른 task가 post되면먼저수행중인 task가종료되면그다음 task가자동으로수행이된다. 그리고서로간에 data가훼손되거나간섭이발생하지않게 scheduler가처리를하게된다. 그러나 task의경우 non-preemtive이기때문에매우긴시간을점유하는것은바람직하지못하다. 따라서 task는가능한한짧게구성을해야한다. 그러나 task가길어질수밖에없는경우도있다. 그러한경우는 task를하나로구성하지말고여러개의 task 로나누어구성을하는것이바람직하다. Post operation은 error_t를 return 한다. task가 task queue에있지않으면 SUCCESS를 return하고 task queue에있으면 (posted but not run yet) FAIL을 return 한다. 일반적으로 component가 task를여러번 run할필요가있다면그 task를 repost해야한다. 다음의두가지코드는비슷하나다른면이있다. - 8 -
command error_t Read.read() { return post readdonetask(); 위의코드는 split-phase call의 one-to-one binding에해당한다. 반면 many-to-one 여러번호출해야할때는다음과같이구현한다. command error_t Read.read() { post readdonetask(); return SUCCESS; Task는소프트웨어 component가하드웨어의 split-phase behavior를 emulation할수있도록해준다. 그러나시스템에서 preemption을관리하기위한 mechanism을제공하는 task 보다더유용한방법이있다. task 끼리는서로에대하여 atomically(mom preemptive하게 ) 수행이되기때문에 task에서수행되는 code는비교적간단해질수있다. 왜냐하면갑작스럽게다른것이수행되거나 data가변경되는위험성이없기때문이다. 그러나인터럽트는다른것이수행되거나 data가중간에변경될수있다. 인터럽트는형재의실행을중지하고 preemptive하게수행을시작한다. nesc와 tinyos에서 function은 task context 외부로부터 preemtive하게수행이가능한데이것은 async keyword를수반한다. 인터럽트는 task에대해비동기적으로수행이일어난다. nesc의규칙은 async function call인 command와 async function signal인 event는 async이다. 즉비동기가아닌 command나 event나 call 할수없다. 비동기가아닌 function은동기가되어있고간단히 sync라고부른다. 기본적으로 command와 event는 sync이다. 만약 sync가아니면 async keyword를붙여준다. Interface 정의는 command와 event가 sync인지 async인지를규정한다. 예를들면다음과같은 interface는모두 synchronous( 동기 ) 이다. interface Send { command error_t send(message_t* msg, uint8_t len); event void senddone(message_t* msg, error_t error); command error_t cancel(message_t* msg); command void* getpayload(message_t* msg); command uint8_t maxpayloadlength(message_t* msg); - 9 -
반면다음은모두 asynchronous( 비동기 ) 이다. Interface Leds { async command void led0on(); async command void led0off(); async command void led0toggle(); async command void led1on(); async command void led1off(); async command void led1toggle(); async command void led2on(); async command void led2off(); async command void led2toggle(); async command uint8_t get(); async command void set(uint8_t val); 모든인터럽트핸들러는 async이고인터럽트핸들러는어떤 sync function도 call graph (make tmote docs 하면생기는 graph) 를포함할수없다. 인터럽트핸들러가 sync function을실행할수있는유일한방법은 task를 post하는것이다. task post는 async operation이지만수행하는 task는 sync이다. 예를들어, UART의 top에서의 packet layer를생각해보자. UART가 byte를받을때, 인터럽트가발생하고인터럽트핸들러에서소프트웨어는 data register의값을읽고 buffer에저장한다. packet의마지막 byte가수신되었을때, 소프트웨어는 packet reception을 signal할필요가있다. 그러나 Receiver interface의 receive event는 sync이다. 그래서마지막 byte의인터럽트핸들러에서 component는 packet reception을알리기위해 task를 post한다. 그러면여기서질문이생긴다. 앞서의설명에따르면 task는 latency 가발생하는데왜 task를사용하는것일까? 왜모든것을 async하게만들지않을까? 그질문에대한답은바로 race condition 때문이다. race condition이란둘이상의 process가동일한 resource를사용하기위하여다투는것으로어느프로세스가먼저 resource를점유하느냐에따라그결과가달라지게된다. 이러한 race condition은 preemptive execution이가능할때현재수행중인 state를바꿈으로써 state의 consistency가보장할수없게될때발생한다. 다음의경우를생각해보면 toggle command는 state bit를 toggle하고값을 return 한다. - 10 -
async command bool toggle() { if (state == 0) { state = 1; return 1; if (state == 1) { state = 0; return 0; state=0에서시작해서다음과같은수행을생각해보자. component가 toggle을 call하면 component는 state의 return 값이 1 이될것이라고예측한다. 그러나인터럽트가종료전에발생하고인터럽트에의해 toggle이 call되면 component가예측하는것과달리 return 값은 0이되어버린다. 이러한문제는 single statement에대해인터럽트가발생할때더심각해질수있다. 예를들면 32bit number가둘이상의 instruction으로쓰거나읽어야할때두 instruction 사이에인터럽트가발생할수있다. 그러면읽는숫자의값이다른새로운숫자와섞여서전혀예측하지못한결과를얻게된다. 이예제의경우는 state variable에서발생하는것으로 data race라부른다. tinyos에서 packet을 abstraction하는 AMStandard 코드의일부를살펴보자. state variable은 component가 busy인지아닌지를나타낸다. command result_t SendMsg.send... { if (!state) { state = TRUE;// send a packet return SUCCESS; return FAIL; 이 command가 async라면 "if (!state)" 조건과 "state=true" 사이에또다른 component가끼어들어 packet을역시전송하려고시도할수있다. 이두번째 call 입장에서는 state가 false라고판단하기때문에 state를 true로바꾸고 packet을보내고 SUCCESS를 return 한다. 그러나그후첫번째 call은 state를 true로바꾸고전송을시작하고 SUCCESS를 return 한다. 그러면두 packet 중의하나만전송이완료되어 error가발생한다. 이러한경우에러의원인을찾기가매우어렵다. 이로인해모든 call sequence 상에서 logical error가발생할수있다. 따라서프로그램을할때다음과같은사실을염두에두어야한다. - 11 -
Code는가능한 synchronous를유지하게한다. Code는 timing이매우중요하거나 timing이중요한다른 component 등에의해사용이는경우에만 async 해야한다. 인터럽트문제는프로그램들이preemption이될수없는 code의단편을수행할수있도록하는방법을필요로한다. nesc는 atmoic statements를통해이러한기능을제공한다. 4.4. Atomic statements nesc의키워드중에 atomic 이라는것이있다. Atomic은사전적으로물질을구성하는최소단위인 " 원자의 " 라는뜻을가지고있다. 이말에서유추할수있듯이더이상쪼갤수없는프로세스를생각해보면, atomic이라는것은오직하나의프로세스만이수행되는것을말한다. 아래의예는 atomic section에의해값의증가가일어나는것이다. command bool increment() { atomic { a++; b = a + 1; atomic block은 block 내의 variable들이 atomically 읽어지거나써지는것을보장한다. 그러나이말이 atomic block이 preemption이될수없는것을의미하지는않는다. Atomic block이 preemption 되지않는다면효율이떨어질것이다. Atomic block이있다할지라도두 code segment 사이에같은 variable이없다면서로 preemption을시킬수있다. 그래야효율이더좋아진다. 다음의예를보면 async command bool a() { atomic { a++; b = a + 1; async command bool c() { atomic { c++; d = c + 1; 이예제에서보면 a() 와 c() 는 atomic으로되어있지만 c() 는 a() 를 preemption 시킬수있다. 왜냐하면 a() 를 preemption 시켜도 c() 의 variable은그결과에영향을받지않기때문이다. 그러나 c() 는 c() - 12 -
자체를 preemption 시킬수없다. nesc는 atomic scetion을제공하기위해 variable들이적절히 protection이되었는지 check하고 protection이되지않았다면 warning을발생시킨다. 예를들면앞예제에서 b와 c가 atomic section으로구성되지않는다면, nesc는자신을preemption시킬가능성때문에 warning을발생시킨다. Variable이 atomic section에의해 protection되는규칙은 variable이 async function으로부터 access가되면 variable은반드시 protection 되어야만한다는것이다. nesc는 flow에 senstive하다. 즉 atomic block을포함하지않는 function이있고이 function이 atomic block 내에서 call이된다면컴파일러는 warning을발생하지않는다. 그러나 atomic block 내에서 call 되지않는다면불필요하게많은 nested atomic block이생길수있다. 대체로 atomic block은인터럽트를 disable하는것과같은종류의수행에만관계가된다. 그래서불필요한 atomic block은 CPU cycle을낭비하게된다. 그래서 nesc는 redundant한 atomic block을제거한다. Atomic block의가장기본적인이용은 component에서의 state transition이다. 대체로 state transition은두부분으로구성되고현재의 state와 call에의해결정이된다. 첫부분은새로운 state로바꾸는것이고두번째는어떤 action을취하는것이다. 즉다음과같다. if (!state) { state = TRUE;// send a packet return SUCCESS; else { return FAIL; 만약 state가 async function에의해사용되어진다면, state transition 을 atomic하게만들필요가있다. 그러나전체 block을 atomic section으로구성하고싶지않다면다음과같이구성을할수있다. uint8_t oldstate; atomic { oldstate = state; state = TRUE; if (!oldstate) {//send a packet return SUCCESS; else { return FAIL; - 13 -
5. Appendix 5.1. nesc Compiler NAME SYNOPSIS ncc - nesc compiler for TinyOS ncc [-target=pc mica mica2 mica2dot...] [-tosdir=dir] [-print-tosdir] [-print-platforms] [-nostdinc] [-board=micasb basicsb micawb...] [-docdir=dir] [-topdir=dir] [-graphviz=y n] [-fnesc-nido-tosnodes=n] [-fnesc-cfile=file] [-fnesc-no-inline [-Wnesc-xxx] [any gcc option] files... DESCRIPTION ncc is an extension to gcc that knows how to compile nesc applications. If invoked on regular C files, it behaves exactly like gcc. When invoked on a nesc component or interface (.nc extension) file it compiles and links (except if the usual -c, -S, -E or -fsyntax-only options are used) that component with the other files specified on the command line. The additional options recognized by ncc over gcc are: -target=x specify the target architecture for this compilation. If pc is specified, the compilation uses the tossim environment and produces a locally executable file. The default target is mica, the possible targets are set by the TinyOS distribution (see the tos/platforms directory). A platform that is not in the TinyOS distribution can be used if its directory is specified with an explicit -I directive (the platform name is taken from the directory's name, platform directories are recognised by the presence of a.platform file). -tosdir=dir specify the location of TinyOS. This location can also be specified with the `TOSDIR' environment variable. If the variable and the option are both given, ncc uses the value specified with the option. If neither the environment variable or option are specified, ncc uses its compiled-in TinyOS directory. -print-tosdir print the TinyOS directory to be used and exit, taking into account the -tosdir option and `TOSDIR' environment variable. No compilation occurs when -print-tosdir is used. -print-platforms print the valid TinyOS platforms, including those made available by explicit -I directives (see -target discussion above). -nostdinc do not automatically include the TinyOS directories in the search path. See the discussion of search paths below for more details. -board=y specify one (or more) sensor boards. This effects the search path and preprocessor symbols. The set of boards is set by - 14 -
the TinyOS distribution (see the tos/sensorboards directory). As with targets, a sensorboard directory can be made available via an explicit -I directive (sensorboard directories are recognised by the presence of a.sensor file). -docdir=dir generate documentation for the compiled component in directory dir. -topdir=dir specify directory paths that should be stripped from the source file names when generating "package names" for the documentation files. The directory above TOSDIR is automatically added, so this option is only needed for directories outside the main TinyOS distribution. -graphviz=y n explicitly enable or disable the use of the graphviz tool in the generated documentation. Without this option, graphviz is enabled iff the `dot' program is found in the current path. Use of graphviz requires `dot'. The documentation generation tool checks the version of `dot', and enables client-side image maps, if supported. -fnesc-tossim-tosnodes=n specify the maximum number of nodes that can be simulated in the tossim environment. -fnesc-cfile=file specify a file in which to save the C code generated when compiling a component. Note: if you specify two components on the command line, then the C code from the second one will overwrite the C code from the first. 5.2. nesc 에서사용하는용어 application 하나이상의컴포넌트로구성되며, 실제센서노드에서실행가능한하난의프로그램 component - NesC를구성하는기본블록으로, 컴포넌트를정의하는 configuration과 module로구분된다. component interface - 2개의컴포넌트사이를연결하기위해정의된포트로, 컴포넌트는여러개의 interface를사용할수있으며, 이 interface를이용하여 command, message가처리된다. 두컴포넌트사이의연결통로 ( 인터페이스 ) 를연결하는것을 wiring 이라고한다. configuration - 하나의새로운컴포넌트를정의하고, 이곳에서사용할다른하부컴포넌트들을선언한다. 그리고이들간의연결 (wiring) 을어떻게정의할것인가에대해기술한다. module - 새로운컴포넌트의동작 acl 다른컴포넌트들과의연동을실제로구현하는곳이다. - 15 -
6. Component 분석 6.1. Micaz ADC ADC (Analog Digital Converter) 는말그대로아날로그의신호를디지털의값으로바꾸어준다는것으로, mote의 sensorboard에서 sensing 된값을 app에서얻고자할때쓰인다. 이번분석에서 hardware는 micaz를썼고, "tos/system/adcm.nc tos/system/adcc.nc", "tos/platform/avrmote/hpladcc.nc" 파일을분석하였다. 먼저, ADCC.nc 파일을살펴보자. 보는바와같이 ADCControl와, ADC[uint8_t port] 를제공하고있다. ADCM과 HPLADCC components를사용하고있고, ADCM에 ADC와 ADCContol에대한구현이있다. 마지막줄은 ADCM의 HPLADC는 HPLADCC의 HPLADC에연결되어있다는것을보인다. ADCC.nc는비교적간단하게구현되어있으므로, 소스를보면이해가가리라생각한다. 다음은 ADCM.nc를보자. module 부분을먼저보겠다. - 16 -
module부분은해당 module에서어떤 interface를사용하고제공하는지를나타내고있다. 보면알기에설명은생략하겠다. ADCM의 implementation 부분을보기전에 ADCM이 HPLADCC에연결되어있기에간략하게 HPLADCC.nc에서제공하는 command와 event를보겠다. 제공하는 command 는다음과같다. 그림 Ⅱ-6-1 Command 종류 이름만봐도어떤기능을하는 command인지대충은알수있을것이다. - 17 -
event 는다음과같다. 그림 Ⅱ-6-2 Event 종류 다음으로 ADCM에서제공하는 command들을살펴본다. command들은다음과같다. 하나하나의자세한의미는잠시후에살펴보겠다. 일단은이런것들이있음을알아놓는다. 이제구체적으로 ADCM 소스를살펴보겠다. module 에서사용할변수들을선언한부분이다. 다음에볼부분은 init() 부분이다. ADC를사용하고자한다면처음으로수행되어져야하는것이다. 쉽게말해서 Sensor로부터값을가져오기위해 pin들을초기화한다고보면되겠다. 소스를보자. 여기서는단순히사용하는변수들을 0 으로초기화시키고, HPLADC.init() 을호출하고있다. 그렇다면 HPLADC.init() 을따라가보자. - 18 -
다음과같이 init_portmap() 을수행하고, 그밑의부분은 avr을콘트롤하기위한부분인데 hardware에관해선그렇게아는바가없어서, 알아보니, 첫번째 outp() 은 ADCSR이라는레지스터에 0x04의값을넣는의미이고, cbi(adcsr, ADSC) 의의미는 ADCSR의 ADSC값을 clear하라는의미이고, sbi는반대로 set하라는의미. 그래서결국엔센싱된값이들어오면 interrupt를하기위해 ADIF,ADIE 값을 set 해주고, 아직 application이값을가지고오지를원치않기때문에 ADSC(ADC start conversion), ADEN(ADC enable) 을 clear한다. init_portmap() 에서하는일을보자. PORTMAPSIZE만큼루프를돌면서 port mapping table을초기화하고있다. ADCControl.setSampleRate(uint8_t rate) 를알아보겠다. ADCM.nc의소스를보면, 다음과같이되어있다. 단순히 HPLADC.setSamplingRate 를호출하고있다는걸볼수있다. 그렇다면 HPLADCC.nc 파일의 setsamplingrate 를보자. - 19 -
ADC의 clock을결정하기위한레지스터가 ADCSR인데초기화시켜줄때 0x04의값을넣어준것을볼수있었다. 모르겠다면 ADC.init() 부분을다시보기바란다. 위에있다. 그값을변경시키기위한 command이다. rate를넘겨받아서 ADCSR에 rate를넣어준다. init_portmap() 부분에서실제의포트를가상의포트테이블로 mapping 시키는것을볼수있었다. 이부분을 remap하기위한 command가있는데, 바로 bindport이다. 실제의포트와가상의포트가어떻게정의되어있는지보기위해선, platform폴더아래에있는해당폴더의 hardward.h 파일과 sensorboard 폴더아래의해당센서보드의폴더의 sensorboard.h 파일을살펴보기바란다. 위의 setsamplingrate와마찬가지로단순히 HPLADC.bindPort를호출하고있다. 가상의포트테이블의 port 번호를넘겨받아서, adcport라는실제 port번호를 mapping 해주고있다. 다음은 ADCM.nc에서중요한 event인 HPLADC.dataReady를알아보겠다. dataready event는 ADC로부터센싱된데이터의값이준비가되면 signal되어진다. 그렇다면우선어떻게 signal 되어지는지 HPLADCC.nc의 ADC.dateReady의소스를살펴보자. - 20 -
위와같이 TOSH_SIGNAL(SIG_ADC) 신호가오면 nesc_enable_interrupt() 를이용하여 interrupt 를주고 ADC.dataReady를 signal 하는것을볼수있다. 위와같은일이일어나면다음으로 ADCM.nc 의 ADC.dataReady가수행된다. ADC.dateReady 의소스를보자. HPLADC.sampleStop() 이라는 cammand가나오는데그것은단순히지금 conversion 할수없기에 sampling을멈추라는것이다. 자세한소스는 HPLADCC.nc에나와있다. 참고하기바란다. samplestop외에도 sampleagain() 이라는 command도있는데그것은이름그대로센싱정보를다시한번 sampling 하는것이다. 마찬가지로 HPLADCC.nc에나와있다. 그리고가장중요한부분을보겠다. application이센싱된값을가져오기를원하면 getdata command를호출해야하는데, 소스는다음과같다. - 21 -
단순히 startget이라는함수를호출하고있는것을볼수있다. startget의두개의인자는말그대로한번 convert, 계속적인 convert 를말하고 port는어떤물리적 port에서센싱된값을가져올것인지를말한다. startget 을보자. state와 port를넘겨받으면해당 port에서센싱된값을받을준비를하는데그부분이 이부분이다. sampleport 를호출하고있는데, 살펴보자. - 22 -
위와같이해당포트로샘플링을하기위해서 ADCSR의 ADEN과 ADSC를 set하고있다. 두비트에관한설명은위에 init 부분에있다. 6.2. TIMER 그림 Ⅱ-6-3 전체시스템의구성및소스전체구성을간단히설명하자면여기서 configuration은 2개가있다. TimerC와 ClockC 두가지가있는데 TimerC는 StdControl, Timer 라는인터페이스를제공한다. 물론그구현은 TimerM이라는 module에있다 (wiring에의해 ). TimerM에서는 Leds, Clock, PowerManagement 라는 interface를제공하는데 ( 여기서제공한다는것은 TimerM에각각의인터페이스의실제구현이들어있다는것이다.) 각각은차례대로 NoLeds, ClockC, HPLPowerManagement로 wiring된다. NoLeds와 HPLPowerManagement는그자체가끝이며 ClockC는 configuration으로 ClockC가제공하는 Clock, StdControl의실제구현은 HPLClock에있다. 정리하자면 Timer( 일반적의미 ) 는 Clock( 일반적의미 ) 을사용하여시간을측정하기때문에 Clock 이라는 interface를사용하고 Timer( 일반적의미 ) 를동작시키는데중간중간에 Power조절을하므로 HPLPowerManagement라는컴포넌트를사용한다. 분석의방법은 main이되는 TimerM을분석하기위해 TimerM에서사용하는 NoLeds, ClockC, HPLPowerManagement라는컴포넌트를먼저알아본다. 그러면각각의컴포넌트가제공하는함수들의역할을알수있고이를 TimerM이어떻게이용하여 Timer를작동시키는가알아볼것이다. 분석의순서는다음과같다. NoLeds -> ClockC -> HPLPowerManagement -> TimerM - 23 -
6.2.1. NoLeds module NoLeds { provides interface Leds; implementation { async command result_t Leds.init() { return SUCCESS; async command result_t Leds.redOn() { return SUCCESS; async command result_t Leds.redOff() { return SUCCESS; async command result_t Leds.redToggle() { return SUCCESS; async command result_t Leds.greenOn() { return SUCCESS; async command result_t Leds.greenOff() { return SUCCESS; async command result_t Leds.greenToggle() { return SUCCESS; async command result_t Leds.yellowOn() { return SUCCESS; async command result_t Leds.yellowOff() { return SUCCESS; async command result_t Leds.yellowToggle() { return SUCCESS; async command uint8_t Leds.get() { return 0; async command result_t Leds.set(uint8_t value) { return SUCCESS; 각 command들을보면 SUCCESS만 return할뿐아무런작업을하지않는다. 또한 TimerM에서도 NoLeds를 wiring 시켜놓은 Leds라는인터페이스를사용하지않는다. 그런점으로볼때이컴포넌트는그냥사용자가필요한일을할때에사용하라고연결시켜놓은컴포넌트로보인다.(Leds라는 interface는 mote 의 led를 On/Off시키는동작을정의해놓은 interface이다.) - 24 -
6.2.2. ClockC Commands result_t setrate (char interval, char scale) Set the clock rate. void setinterval(uint8_t value) Set clock interval Parameters: value - New clock interval Returns: none void setnextinterval(uint8_t value) Set clock interval at next clock interrupt time Parameters: value - New clock interval Returns: none uint8_t getinterval(void) Get clock interval Returns:current clock interval uint8_t getscale(void) Get clock scale Returns:current clock scale level void setnextscale(uint8_t scale) Set clock scale at next clock Parameters: scale - New clock scale interrupt time Returns: none result_t setintervalandscale(uint8_t interval, uint8_t scale) Set both clock interval and scale Parameters: interval - New clock interval scale - New clock scale Returns: SUCCESS or FAILED uint8_t readcounter(void) Read HW clock counter void setcounter(uint8_t n) Set HW clock counter to a specified value Parameters: n - Value to write to TCNT0 Returns: None void intdisable(void) Disable Clock interrupt void intenable(void) Enable Clock interrupt Events result_t fire(void) An event sent when the clock goes off. 이컴포넌트에서 TimerM에서사용하는 command, event만본다면다음과같다. setinterval - 다음 Clock interrupt time부터 clock interval을바꿔준다. getinterval - Clock interval을가져온다. readcounter - H/W clock counter를읽어온다. setrate- clock Rate를설정한다. fire - clock이종료되었을때호출되는이벤트 여기서 setrate 의방식을잠깐살펴보자. result_t setrate (char interval, char scale) - 25 -
먼저인자로들어가는 interval 은 tick 수를나타내며 scale 은 platform 의영향을받는값으로다음의리스트와같다. Clock scale 0 - off 1-32768 ticks/second 2-4096 ticks/second 3-1024 ticks/second 4-512 ticks/second 5-256 ticks/second 6-128 ticks/second 7-32 ticks/second 이 command는몇초마다 clock을발생시킬건지를결정한다. 예를들어 setrate(160,7) 이라고했을때 160ticks/32ticks/second 160/32=5 매 5초마다 clock가발생한다. 6.2.3. HPLPowerManagement Provided Interfaces PowerManagement result_t Enable(void) result_t Disable(void) Variables bool disabled = TRUE Function Index uint8_t getpowerlevel(void) task void doadjustment(void) async command uint8_t PowerManagement.adjustPower (void) command result_t Enable(void) command result_t Disable(void) 이컴포넌트와 wiring 된 interface 는 adjustpower 라는 command 하나만을정의한다. 또한 TimerM 에서사용하는 command 도 adjustpower 하나이다. 이 command 는 hardware 의현재상태에따라 Power 를조절한다. 어떠한상태가있고어떻게조절하는지는 hardware 에대한지식이필요한부분이라서넘어가고대충코드로짐작을해보면 POWER_SAVE, EXT_STANDBY 등의상태가있는것으로보인다. - 26 -
6.2.4. TimerM TimerC가제공해주는 interface가 2개가있는데 StdControl은기본 init, start, stop가정의되어있는 interface이고, interface Timer를알아본다. Commands result_t start (char type, uint32_t interval) Start the timer. result_t stop (void) Stop the timer, preventing it from firing again. Events result_t fired(void) The signal generated by the timer when it fires. 기본커멘드로 start 와 stop, 이벤트로 fired 가있다. start 는타이머를시작하는것이고 stop 는타이머를종료하는커맨드이다. 타이머를시작하고셋팅된시간이경과하면 fired 이벤트가발생해원하는작업을코딩할수있다. 이제 TimerM을보자 Provided Interfaces Timer StdControl 그림 Ⅱ-6-4 먼제제공되어지는 interface 는기본 interface 인 StdControl 과위에서살펴본바있는 Timer interface 2 가지이다. 이제두가지 interface 들의 command 들과 event 들의구현이있는 TimerM 을보자. Required Interfaces Leds Clock PowerManagement Provided Interfaces Timer StdControl - 27 -
Variables uint32_t mstate uint8_t setintervalflag uint8_t mscale uint8_t minterval int8_t queue_head int8_t queue_tail uint8_t queue_size uint8_t queue[num_timers] volatile uint16_t interval_outstanding struct TimerM.timer_s { uint8_t type; int32_t ticks; int32_t ticksleft; mtimerlist[num_timers] Function Index command result_t StdControl.init (void) command result_t StdControl.start (void) command result_t StdControl.stop (void) command result_t Timer.start (uint8_t id, char type, uint32_t interval) static void adjustinterval(void) command result_t Timer.stop (uint8_t id) event result_t Timer.fired (uint8_t id) void enqueue(uint8_t value) uint8_t dequeue(void) task void signalonetimer(void) task void HandleFire(void) async event result_t Clock.fire (void) implementation { uint32_t mstate; // each bit represent a timer state uint8_t setintervalflag; uint8_t mscale, minterval; int8_t queue_head; int8_t queue_tail; uint8_t queue_size; uint8_t queue[num_timers]; volatile uint16_t interval_outstanding; struct timer_s { uint8_t type; // one-short or repeat timer int32_t ticks; // clock ticks for a repeat timer int32_t ticksleft; // ticks left before the timer expires mtimerlist[num_timers]; - 28 -
enum { maxtimerinterval = 230 ; type는타이머를계속반복할지한번만실행할지결정하는속성이다. tos interfaces Timer.h를보면값들이정의되어있다. 그리고 maxtimerinterval을 230으로줬는데그이유는이하드웨어가 8bit timer를사용하므로 0~255까지를셈할수있기때문에적당선으로 230을잡아준것같다. #ifndef NTIMERS #if NESC >= 110 #define NTIMERS uniquecount("timer") #else #define NTIMERS 12 #endif #endif enum { TIMER_REPEAT = 0, TIMER_ONE_SHOT = 1, NUM_TIMERS = NTIMERS ; tos interfaces Timer.h command result_t StdControl.init() { mstate=0; setintervalflag = 0; queue_head = queue_tail = -1; queue_size = 0; mscale = 7; minterval = maxtimerinterval; return call Clock.setRate(mInterval, mscale) ; 프로그램을실행할때 timer state, queue, timer, clock들을초기화한다. queue는다들안다고생각하고나머지변수들을보면 mstate - 개개의타이머가실행중인지를나타내는플래그변수이다. setintervalflag - Timer가시작할때 0, 종료 (fire) 될때 1로셋팅한다. mscale - hardware가초당몇번의 tick을발생시키는지결정 minterval - 타이머를몇번의 tick발생마다종료시킬것인지결정 - 29 -
command result_t StdControl.start() { return SUCCESS; 별다른동작없이그냥시작하면 SUCCESS를반환한다. command result_t StdControl.stop() { mstate=0; minterval = maxtimerinterval; setintervalflag = 0; return SUCCESS; 종료시기본변수들을초기화시켜주고 SUCCESS를반환한다. command result_t Timer.start[uint8_t id](char type, uint32_t interval) { uint8_t diff; if (id >= NUM_TIMERS) return FAIL; if (type > TIMER_ONE_SHOT) return FAIL; if ((type == TIMER_REPEAT) && interval <= 2) return FAIL; mtimerlist[id].ticks = interval ; mtimerlist[id].type = type; atomic { diff = call Clock.readCounter(); interval += diff; mtimerlist[id].ticksleft = interval; mstate =(0x1L<<id); if (interval < minterval) { minterval=interval; call Clock.setInterval(mInterval); setintervalflag = 0; call PowerManagement.adjustPower(); return SUCCESS; TIMER_REPEAT일때 interval에제한을두는이유는하드웨어 clock이비교값을셋팅하는동안에증가될수있기때문이란다.( 이사실이왜문제가되는지정확한이유는아직잘모르겠다.) 현재 clock counter값을읽어와 interval에더해준다. 그리고그결과값이최대 interval값보다작으면그값을 Clock의 interval 로설정하고 power를조정한다. 중간에 timer의 identifyer인 id - 30 -
를이용해 mstate의 id번째 bit를 1로셋팅해주는코드도보인다. mtimerlist는다음의구조체를가지는배열이다. struct timer_s { uint8_t type; // one-short or repeat timer int32_t ticks; // clock ticks for a repeat timer int32_t ticksleft; // ticks left before the timer expires mtimerlist[num_timers]; 여기서 type 은 timer 의동작방식 (ONE-SHOT, REPEAT), ticks 는 timer 가동작하는 clock tick, ticksleft 는 timer 가종료되기까지남은 ticks 를의미한다. async event result_t Clock.fire() { atomic { /* DCM: Once we've posted HandleFire(), don't post it again until * the original one is handled. This prevents the task queue * from getting flooded when minterval is small. */ if (interval_outstanding == 0) post HandleFire(); else dbg(dbg_error, "Don't post handle fire, we're not ready n"); /* DCM: Keep track of the interval since the last interrupt */ interval_outstanding += call Clock.getInterval() + 1; return SUCCESS; Clock 이발생할때마다실행되는이벤트인데여기서는 interval_outstanding 이라는 flag 를둬서 HandleFire 가한번에하나만 post 될수있도록하는데그이유는 interval 이작을경우 queue 를관리하기위함이다. 만약 post 된뒤수행되고있는 task 가없다면 HandleFire 를 post 하고, post 되었으나아직수행은되지않은 task 가있다면 (interval_outstanding 가 0 이아닐경우 ) Clock 의 interval 을읽어와 1 을더한값을 interval_outstanding 에더해준다. task void HandleFire() { uint8_t i; uint16_t int_out; - 31 -
setintervalflag = 1; /* DCM: read the number of ticks elapsed since the last firing * was handled. */ atomic { int_out = interval_outstanding; interval_outstanding = 0; if (mstate) { for (i=0;i<num_timers;i++) { if (mstate&(0x1l<<i)) { mtimerlist[i].ticksleft -= int_out; if (mtimerlist[i].ticksleft<=2) { /* DCM: only update the timer structure if the * signalonetimer() task was able to be posted. */ if (post signalonetimer()) { if (mtimerlist[i].type==timer_repeat) { mtimerlist[i].ticksleft += mtimerlist [i].ticks; else {// one shot timer mstate &=~(0x1L<<i); enqueue(i); else { dbg(dbg_error, "TimerM: Have to wait another time interval. n"); /* DCM: wait another interval in hopes that * the task queue will clear out. */ mtimerlist[i].ticksleft = minterval; /* DCM: don't bother adjusting the interval if another interrupt * is hot on our tail. */ atomic int_out = interval_outstanding; if (int_out == 0) adjustinterval(); - 32 -
마지막 firing 발생을처리한이후의시간이 interval_outstanding에있다그때문에이값을 int_out에넣고 interval_outstanding에 0을넣어준다. 0을넣어줘야다음의 Clock.fired() 이벤트를처리할수있기때문에이러한작업을한다. 첫번째조건에서어느 timer에서발생한것인지를판단하며다음조건에서는 signalonetimer() 가 post될수있는지를체크한다. 만약 post될수없는상황이라면 ticksleft를 minterval로셋팅함으로써다음 firering을기다린다. signalonetimer() 의 post가성공하면 timer의 type이 ONE SHOT인지 REPEAT인지를판단하여 REPEAT이면 ticks를다시설정해주고 one shot이면 mstate의해당타이머 bit를해제한다. queue에 i를삽입하고루프는끝이나며이상의과정중에 interval_outstanding이생기지않았으면 adjustinterval() 을호출한다. task void signalonetimer() { uint8_t itimer = dequeue(); if (itimer < NUM_TIMERS) signal Timer.fired[itimer](); 이 task 는 HandleFire() 에서 queue 에넣은 timer identifyer 를꺼내어해당 timer 의 fired event 를 signal 시킨다. void enqueue(uint8_t value) { if (queue_tail == NUM_TIMERS - 1) queue_tail = -1; queue_tail++; queue_size++; queue[(uint8_t)queue_tail] = value; uint8_t dequeue() { if (queue_size == 0) return NUM_TIMERS; if (queue_head == NUM_TIMERS - 1) queue_head = -1; queue_head++; queue_size--; return queue[(uint8_t)queue_head]; enqueue(...) 와 dequeue() 는일반 queue 연산과같다 - 33 -
default event result_t Timer.fired[uint8_t id]() { return SUCCESS; Timer.fired event 는 SUCCESS 를반환한다사용자는이제이 event 를 wiring 하여필요한작업을코딩하면된다. static void adjustinterval() { uint8_t i, val = maxtimerinterval; if ( mstate) { for (i=0;i<num_timers;i++) { if ((mstate&(0x1l<<i)) && (mtimerlist[i].ticks Left <val )) { val = mtimerlist[i].ticksleft; /* DCM: If the interval is set to be less than the current * counter value, the timer will count an extra 256 ticks before * hitting the interrupt. Thus, we check for this condition * and avoid it. */ /* PAL: This piece of code sets a maximum interrupt rate * that TimerM will request for continuous timers. TimerM * will never request an interrupt less than 3ms from the * current time; it therefore returns FAIL on continuous * timers with an interval <= 2 (see Timer.start()). */ atomic { i = call Clock.readCounter() + 3; if (val < i) { val = i; minterval = val; call Clock.setInterval(mInterval); setintervalflag = 0; else { atomic { minterval=maxtimerinterval; call Clock.setInterval(mInterval); setintervalflag = 0; call PowerManagement.adjustPower(); - 34 -
동작중인 timer 가있다면 clock 의 interval 을조정해주고없다면 interval 을최대값으로셋팅한뒤 power 를조정해준다. command result_t Timer.stop[uint8_t id]() { if (id>=num_timers) return FAIL; if (mstate&(0x1l<<id)) { // if the timer is running atomic mstate &= ~(0x1L<<id); if (!mstate) { setintervalflag = 1; return SUCCESS; return FAIL; //timer not running timer[id] 가동작중이라면 mstate 를해제하고 setintervalflag 를 1 로셋팅해준다. 만약 timer[id] 가동작중이지않다면 FAIL 을반환할것이다. 이상으로 TimerC, TimerM 분석을마친다. ticksleft 값의변경이라든가 setintervalflag 값의변경등 Timer 의동작에관해아직이해가부족한부분이있는데이는추후 update 한다. - 35 -
6.3. GENERICCOMM 그림 Ⅱ-6-5 전체시스템구성및요소 GenericComm은통신 (Communication - UART, RADIO) 을하기위한일반화된컴포넌트이다. 다음은기본인터페이스이다. Interface StdControl : 기본컨트롤인터페이스 SendMsg : 해당매체 (Radio or Uart) 로 Message를송신 ReceiveMsg : 해당매체 (Radio or Uart) 로부터 Message를수신 function activity : AMStandard에정의된함수 (lsatcount를반환 ) senddone : AMStandard에정의된함수 (Message 송신이성공했을결우발생되는이벤트로 success를반환한다.) implementation { // CRCPacket should be multiply instantiable. As it is, I have to use // RadioCRCPacket for the radio, and UARTNoCRCPacket for the UART to // avoid conflicting components of CRCPacket. components AMStandard, RadioCRCPacket as RadioPacket, UARTFramedPacket as UARTPacket, NoLeds as Leds, TimerC, HPLPowerManagementM; Control = AMStandard.Control; SendMsg = AMStandard.SendMsg; ReceiveMsg = AMStandard.ReceiveMsg; senddone = AMStandard.sendDone; activity = AMStandard.activity; - 36 -
AMStandard.TimerControl -> TimerC.StdControl; AMStandard.ActivityTimer -> TimerC.Timer[unique("Timer")]; AMStandard.UARTControl -> UARTPacket.Control; AMStandard.UARTSend -> UARTPacket.Send; AMStandard.UARTReceive -> UARTPacket.Receive; AMStandard.RadioControl -> RadioPacket.Control; AMStandard.RadioSend -> RadioPacket.Send; AMStandard.RadioReceive -> RadioPacket.Receive; AMStandard.PowerManagement -> HPLPowerManagementM.PowerManagement; 위의 wiring을보면 StdControl, SendMsg, ReceiveMsg각각은 Timer, UART, Radio의 Stdcontrol, SendMsg, ReceiveMsg로 wiring되고모든구현은 AMStandard에있다는것을알수있다. AMSTANDARD 그림 Ⅱ-6-6 AMStandard 이컴포넌트는 UART 또는 RADIO로패킷을전송하는컴포넌트이다. 위의구성도는오실로스코프구성도의일부분이다. 여기서중점적으로알아볼것은 AMStandard의기본동작분석이다. TimerC는하나의컴포넌트로따로분석이되어있으므로생략하겠다. 그러면 PacketSink와 UARTFramedPacket 컴포넌트가남게되는데 PacketSink 는오실로스코프에서 Radio를사용하지않기때문에 RadioCRCPacket 을사용하는대신아무런작업을하지않는컴포넌트로구현만해놓은컴포넌트이다. 따라서 RADIO 통신은 RadioCRCPacket이라는컴포넌트를따로분석하면서알아보겠다. UARTFramedPacket도마찬가지로 AMStandard의분석을마친후따로분석한다. - 37 -
Require Interfaces result_t senddone(void) StdControl UARTControl BareSendMsg UARTSend ReceiveMsg UARTReceive StdControl RadioControl BareSendMsg RadioSend ReceiveMsg RadioReceive StdControl TimerControl Timer ActivityTimer PowerManagement Provided Interfaces StdControl Control SendMsg ReceiveMsg uint16_t activity(void) Variables bool state TOS_MsgPtr buffer uint16_t lastcount uint16_t counter Function Index command bool Control.init (void) command bool Control.start (void) command bool Control.stop (void) command uint16_t activity(void) void dbgpacket(tos_msgptr data) result_t reportsenddone(tos_msgptr msg, result_t success) event result_t ActivityTimer.fired (void) event result_t SendMsg.sendDone (uint8_t id, TOS_MsgPtr msg, result_t success) event result_t senddone(void) task void sendtask(void) command result_t SendMsg.send (uint8_t id, uint16_t addr, uint8_t length, TOS_MsgPtr data) event result_t UARTSend.sendDone (TOS_MsgPtr msg, result_t success) event result_t RadioSend.sendDone (TOS_MsgPtr msg, result_t success) TOS_MsgPtr received(tos_msgptr packet) event TOS_MsgPtr ReceiveMsg.receive (uint8_t id, TOS_MsgPtr msg) event TOS_MsgPtr UARTReceive.receive (TOS_MsgPtr packet) event TOS_MsgPtr RadioReceive.receive (TOS_MsgPtr packet) - 38 -
이제 AMStandard 의각함수들을알아보자 command bool Control.init() { result_t ok1, ok2; call TimerControl.init(); ok1 = call UARTControl.init(); ok2 = call RadioControl.init(); state = FALSE; lastcount = 0; counter = 0; dbg(dbg_boot, "AM Module initialized n"); return rcombine(ok1, ok2); 초기화 하는 부분이다. UARTControl과 RadioControl, 그리고 TimerControl을초기화한다. counte는읽은 packet수를의미하는변 수이다. UARTControl과 RadioControl이모두정상적으로초기화되면 true를반환한다. command bool Control.start() { result_t ok0 = call TimerControl.start(); result_t ok1 = call UARTControl.start(); result_t ok2 = call RadioControl.start(); result_t ok3 = call ActivityTimer.start(TIMER_REPEAT, 1000 * TIMER _PRESCAL); // HACK -- unset start here to work around possible lost calls to // senddone which seem to occur when using power management. SRM 4.4.03 state = FALSE; call PowerManagement.adjustPower(); return rcombine4(ok0, ok1, ok2, ok3); Timer, UART, Radio를시작하고 Timer를반복적으로동작시킨다. 타이머의주기는 1000*TIMER_PRESCAL이고 TIMER_PRESCAL 속성은위에서정의되었다.power를조정하고실패한동작이없으면 rtue를반환한다. - 39 -
command bool Control.stop() { result_t ok1 = call UARTControl.stop(); result_t ok2 = call RadioControl.stop(); result_t ok3 = call ActivityTimer.stop(); // call TimerControl.stop(); call PowerManagement.adjustPower(); return rcombine3(ok1, ok2, ok3); application이종료될때 UART, Radio를종료하고작동하고있는 Timer를정지시킨다. Timer의 Control을정지시키는부분은주석처리되어있는데왜주석처리해뒀는지는모르겠다. 모든동작이성공하면 true를반환한다. command result_t SendMsg.send[uint8_t id](uint16_t addr, uint8_t length, TOS_MsgPtr data) { if (!state) { state = TRUE; if (length > DATA_LENGTH) { dbg(dbg_am, "AM: Send length too long: %i. Fail. n", (int)length); state = FALSE; return FAIL; if (!(post sendtask())) { dbg(dbg_am, "AM: post sendtask failed. n"); state = FALSE; return FAIL; else { buffer = data; data->length = length; data->addr = addr; data->type = id; buffer->group = TOS_AM_GROUP; dbg(dbg_am, "Sending message: %hx, %hhx n t", addr, id); dbgpacket(data); return SUCCESS; return FAIL; - 40 -
이코드를보면 state의쓰임을알수있다. state는현재프로세스가어떤작업을 ( 여기서는 message sending) 하고있는지를알려주는변수이다. 즉 message를전송할때에는 TRUE로셋팅해서다른작업으로부터보호해주고끝나면 FALSE로셋팅해작업이끝났음을알려준다. 이러한과정을거치는이유는 buffer를사용하기때문인것같다. post를사용하여메시지를전송하는데 UART로전송할것인지 Radio로전송할것인지는 sendtask() 에서 addr을보고판단한다. task void sendtask() { result_t ok; TOS_MsgPtr buf; buf = buffer; if (buf->addr == TOS_UART_ADDR) ok = call UARTSend.send(buf); else ok = call RadioSend.send(buf); if (ok == FAIL) // failed, signal completion immediately reportsenddone(buffer, FAIL); 위의 if문장에서 UART로전송할것인지 Radio로전송할것인지를판단한다. 여기서사용되는 TOS_MsgPtrd은 TOS_Msg의포인터구조체이다. typedef struct TOS_Msg { /* The following fields are transmitted/received on the radio. */ uint16_t addr; uint8_t type; uint8_t group; uint8_t length; int8_t data[tosh_data_length]; uint16_t crc; /* The following fields are not actually transmitted or received * on the radio! They are used for internal accounting only. * The reason they are in this structure is that the AM interface * requires them to be part of the TOS_Msg that is passed to * send/receive operations. */ uint16_t strength; uint8_t ack; uint16_t time; uint8_t sendsecuritymode; uint8_t receivesecuritymode; TOS_Msg; - 41 -
전송이완료되면다음의이벤트가각각발생한다. event result_t UARTSend.sendDone(TOS_MsgPtr msg, result_t success) { return reportsenddone(msg, success); event result_t RadioSend.sendDone(TOS_MsgPtr msg, result_t success) { return reportsenddone(msg, success); reportsenddone(msg, success) 함수의원형이다. result_t reportsenddone(tos_msgptr msg, result_t success) { state = FALSE; signal SendMsg.sendDone[msg->type](msg, success); signal senddone(); return SUCCESS; state를 FALSE로바꾸어주고다음의두개함수를 signal하는데 default event result_t SendMsg.sendDone[uint8_t id](tos_msgptr msg, result_t success) { return SUCCESS; default event result_t senddone() { return SUCCESS; 둘다특별한작업없이 SUCCESS만 return한다. 이상으로메시지전송작업은완료된다. 이제메시지를수신할때를분석해보자. UART나 Radio로메시지가들어오면다음의이벤트들이발생하여 received를호출하게된다. event TOS_MsgPtr UARTReceive.receive(TOS_MsgPtr packet) { // A serial cable is not a shared medium and does not need group-id // filtering packet->group = TOS_AM_GROUP; return received(packet); event TOS_MsgPtr RadioReceive.receive(TOS_MsgPtr packet) { return received(packet); - 42 -
그럼이제 received를살펴보겠다. TOS_MsgPtr received(tos_msgptr packet) attribute ((C, spontaneous)) { uint16_t addr = TOS_LOCAL_ADDRESS; counter++; dbg(dbg_am, "AM_address = %hx, %hhx; counter:%i n", packet ->addr, packet->type, (int)counter); if (packet->crc == 1 && // Uncomment this line to check crcs packet->group == TOS_AM_GROUP && (packet->addr == TOS_BCAST_ADDR packet->addr == addr)) { uint8_t type = packet->type; TOS_MsgPtr tmp; // Debugging output dbg(dbg_am, "Received message: n t"); dbgpacket(packet); dbg(dbg_am, "AM_type = %d n", type); // dispatch message tmp = signal ReceiveMsg.receive[type](packet); if (tmp) packet = tmp; return packet; attribute 는이 fuction에속성을지정해주는 gcc의문법인데 spontaneous는다른일반 function에서이 function을사용할수없다는것을의미한다. 여기서의문은 received는 UARTReceive.receive 와 RadioReceive.receive에서호출을했다는것이다. 그게가능한이유는 UARTReceive.receive와 RadioReceive.receive가이벤트핸들러에의해호출되는것이기때문에프로그래머가임의로호출했다하더라도그안에서호출되는 function이기때문에위의특성과맞아떨어진다고볼수있다. nesc에서는 3가지속성을지원하는데설명은다음과같다. C의정확한의미는잘모르겠다. - 43 -
C: This attribute is used for a C declaration or definition d at the top-level of a module (it is ignored for all other declarations). It specifies that d 's should appear in the global C scope rather than in the module 's per-component-implementation scope. This allows d to be used (e.g., called if it is a function) from C code. spontaneous: This attribute can be used on any function f (in modules or C code). It indicates that there are calls f that are not visible in the source code. Typically, functions that are called spontaneously are interrupt handlers, and the C main function. Section 9 discusses how the nesc compiler uses the spontaneous attribute during compilation. combine(fnname): This attribute specifies the combining function for a type in a typedef declaration. The combining function specifies how to combine the multiple results of a call to a command or event which has "fan-out ". 지금이 function에서는 TOS_AM_GROUP일때에만 ReceiveMsg.receive 이벤트를발생시킨다. ReceiveMsg.receive는다음과같은데받은메시지를그대로 return할뿐이다. 즉프로그래 ] 머가어떤작업을하도록재정의해주지않으면받은메시지를그대로 return하는 default event 가호출되는것이다. default event TOS_MsgPtr ReceiveMsg.receive[uint8_t id](tos_msgptr msg) { return msg; counter는받은메시지의개수정보를유지하는데 ( 패킷을받을때마다증가시킨다.) timer를동작시켜서 fire될때마다 lastcount에저장을하고 count는 0으로셋팅한다. event result_t ActivityTimer.fired() { lastcount = counter; counter = 0; return SUCCESS; activity는 lastcount를반환한다. command uint16_t activity() { return lastcount; 이상으로 AMStandard.h의분석을마치겠다. - 44 -
6.4. Micaz Surge Routing 먼저 surge app는 multihop으로동작을하며센서노드가센싱데이터를자신의부모에게전달하여결국에는 sink node가센싱된데이터를수집하여 sink node에연결된uart(pc) 로전송한다. 그림 Ⅱ-6-7 SurgeApp 구조 < 그림 1> 은 surge app 의구조를나타낸다. Main에서시작되는 StdControl은각 Component들에연결되어있다. Main이시작할때 Main에연결된Component의 init() 호출그후 start() 호출 그림에서 Main에연결된 Component를살펴보면TimerC, Photo, GenericCommPromiscuous, SurgeM, MultiHopRouter, Bcast, QueuedSend 이다. 왜이렇게되는지직접소스를보고확인해보자. 여러분도이문서만보지말고, 직접꼭해당파일을찾아가서확인 해보기바란다. - 45 -
그림 Ⅱ-6-8 Main 구조 tos/system 폴더에있는 RealMain.nc 파일을보자. 이제알수있을것이다. StdControl을이용해 init() 후에 start() 를호출하고있다. 이부분은모든App에거의공통적이므로필히알아두도록하자. 이제본격적으로 Routing 관련된부분으로들어가보겠다. 그림 Ⅱ-6-9 MultiHopRouter 구조 MultiHopRouter 과 MultiHopEngineM 은점선으로연결되어있는데, 이 - 46 -
것을 (=)Equal 관계를의미한다. 이것은곧실제로 MultiHopRouter에서는하는일이없고 MultiHopEngineM에서모든일을담당한다는뜻이다. MultiHopEngineM에서는 MultiHopLEPSM에서제공 (provide) 하는 function들을사용해서 surge에서보내는 SurgeMsg 관련된일을담당하여처리한다. 먼저 message의구조를알아보고넘어가도록하겠다. TinyOS에서는 radio를통해전송하는데이터에대해서일반적인 message 형태를제공하는데이것을 Tos_Msg 라고한다. 아래그림에서보듯이 Tos_Msg내에 Multihop_Msg가 packing 되어있고, Multihop_Msg내에 Surge_Msg가 packing 되어있다. 그림 Ⅱ-6-10 Message 구조 - 47 -
Surge Message 의타입은surge.h 파일에서보면 17인것을확인할수있다. enum { AM_SURGEMSG = 17 Multihop Message 는 multihop.h 에나와있다. enum { AM_MULTIHOPMSG = 250 Message에대한부분은이정도로마무리하기로하고, 자세한것은소스를보기바란다. 이제 Surge.nc, SurgeM.nc, Surge.h 파일을살펴볼차례이다. SurgeM.nc를보자. - 48 -
이렇게되어있을것이다. Init() 에서 initialize() 를호출하고각종변수를초기화해준다. 그후 start() 가호출될때 timer를 start해준다. 이부분이문제다. Make pc 했을때생기는에러는 TIMER_PRESCAL 을쓰면서 define을해놓지않아서발생하는문제이다. 그렇다면 TIMER_PRESCAL을왜쓸려고했을까? 그건원래 UCB에서의소스를보고확인할수있는데, 원본은아래와같다. Command result_t StdControl.start() { uint16_t randomtimer; call CC2420Control.SetRFPower(15); call MacControl.enableAck(); randomtimer = (call Random.rand() & 0xffff) +1 ; return call Timer.start(TIMER_ONE_SHOT, randomtimer); CC2420의 RF power를설정하고 MAC protocol에 ack를 enable 시키고 random하게 timer를 ONE_SHOT으로동작시킨다. ONE_SHOT은한번만실행한다는얘기다. 그럼여기서왜 randomtimer를쓰는이유는? 수많은노드들이초기에같이동작하게되면동시에겹치게될것이다. 그래서처음한번은랜덤한시간을가지고센싱을하고그후부터는 TIMER_REPEAT 로일정간격으로수행하면될것이다. Random 을쓰기위해서는 lib에보면제공해주는라이브러리가있다. 이제어떻게쓰는지상세히보자. 우선 component는 RandomLFSR를쓰면된다. 이건찾아보면알수있다. - 49 -
RandomLFSR를쓰기위해서 SurgeM.nc에서 uses interface Random; 을추가해준다. 다음 Surge.nc의 configuration-implementation에서 component 추가를해준다. components Main, SurgeM, TimerC, LedsC, NoLeds, Photo, RandomLFSR, 그밑에다가 wiring 하자. SurgeM.Random -> RandomLFSR; 이것으로끝이다. 이제부터우리는 RandomLFSR component를 Random이란interface를통해서사용할수있다. RandomLFSR 내에는이미 Random interface가제공되어져있다. rand() 함수도확인해보자. 모든데모 App에거의공통적으로들어가는부분인데, 이것을간소화해서 TIMER_PRESCAL 로대체해서쓸려고했던것같다. 그런데문제는 TIMER_PRESCAL을정의해놓은부분이들어있지않다는것이다. 다시원소스분석으로들어가겠다. - 50 -
Timeout event가발생하면 ADC로부터센싱된데이터를가져오라는말이다. 센싱이끝나서데이터가준비되면발생하는 event는 dataready() 이다. 센싱된데이터를 radio 로보내기위해서 task 를 queue 에 post 한다. Task queue에들어간 SendData() 는 queue에쌓인순서대로수행되게된다. SendData() 에서는 Tos_Msg내에 MultiHop_Msg 밑에 Surge_Msg구조 - 51 -
체에맞게데이터를넣는다. getbuffer() 를보면나와있다. 이것은 MultiHopEngineM.nc 파일의 Data의처음부분을넘겨준다. 그럼그다음에다가 Surge_Msg를넣으면될것이다. Send.send() 도 MultiHopEngineM.nc 에구현되어있다. MultiHopEngineM.nc에서는자체적으로시작하는내용보다는 event에의한동작과 SurgeM 에서의 function호출에의한동작이주가되므로먼저 MultiHopLEPSM.nc 를살펴보도록하자. - 52 -
먼저 AM.h 에가보면각주소에대한설정이되어있다. 위의소스를보면자신이 BASE_STATION_ADDRESS를가지면, 즉 sink node일때와아닐때두가지에따라초기화값이달라진다. 먼저 sink node 일때부모는 UART(0x007e) 가되어야할것이다. Sink node가아닐때는부모가tos_bcast_addr(0xffff) 가되어야할것같다. 다른소스에보면그렇게되어있다. 찾아서확인해보길 ~ 그외에다른변수중에 HopCount설정부분이 sink node 일때는 0, 아닐때는 ROUTE_INVALID (0xff) 로설정하는부분이틀리다. Init() 후에 start() 가호출될것이다. 찾아가서보자. 위에서 gupdateinterval은 init() 에서 gupdateinterval = DATA_TO_ROUTE_RATIO * DATA_FREQ; 라고설정되어있다. 여기서한가지의문은 Start() 에서왜 post TimerTask() 를할까? 왜그런지는 TimerTask() 를살펴보면알수있다. 우선쉽게얘기하자면처음시작하면 Beacon을받아서자신의 sink node가누구인지, 이웃들은누가있는지등의 routing정보를가졌을때 TimerTask() 를통해서 table갱신과부모를선정하게된다. 그런데시작하자마자 TimerTask() 호출해봤자무엇이있겠는가처음타이머한번돌고타임아웃되면하는게맞을거같다. 그래서 fired() 에서수행을하고있다. - 53 -
함수이름만봐도알수있다. UpdateTable(), chooseparent() 여러분도다짐작할수있을것이다. - 54 -
위에두부분은여러분이천천히보면충분히이해할수있을것이다. 이해를돕자면 이러하다. 마지막에있는 post SendRouteTask() 를살펴보자. - 55 -
이부분을보면 MultiHop_Msg안에 Route_Packet을집어넣는부분이다. 앞에서나온 message 구조를다시확인해보자. neighbortable중에서 NBRFLAG_VALID 한것만을 sorttable에넣는것을볼수있다. 그외 route packet 필드들을채우는과정과마지막에는MultiHop message의필드들을채우고있다. 그외에 sortentry에넣어뒀던값들이 RoutePacket내에 estentries에 SeqOf로들어가는것을볼수있다. 다음으로 RoutePacket 을받으면어떻게될까? - 56 -
Event로 ReceiveMsg.receive() 가발생한다. 소스는아래와같다. 지금까지의역과정이므로지금까지잘따라왔다면이해하기쉬울것이다. 받는부분은이걸로끝내고처음에보낸 RoutePacket에대한응답으로 MAC level 에서의ACK를받았을때를알아보자. ACK 를받았을때발생되는이벤트는 SendMsg.sendDone() 이다. gfsendroutebusy는 SendRouteTask() 에서 TRUE로셋팅했다가, 여기서 FALSE로바꾼다. 위에가서확인하기바란다. 그렇다면이놈의의미는 packet을전송하기위해저장하는 buffer가이제다전송되었으니다른 packet을전송하기위해buffer를사용해도된다는의미로해석할수있다. 지금까지는초기화시 route packet을주고받는것에대한설명이었다. 이제부터는 surge message를주고받는부분을살펴보자. Surge Message에대한처리는 MultiHopEngineM.nc에서주로다루고있다. 그외에 MultiHopLEPSM.nc 에서설명하지않은부분들이 Surge_Msg 관련해서사용되는함수들이다. MultiHopEngineM의초기화부분을먼저보자. - 57 -
Init() 에서 SubControl.init() 을호출하는것을볼수있다. 이것은 MultiHopLEPSM.init() 을호출하는것이다. Initialize() 에서는 forwarding을위한 2중 buffer를초기화한다. 아래와같이버퍼와 head, tail position이선언되어있다. 앞에서말했듯이 SurgeM에서 Surge_Msg를 send() 하면 MultiHopEngineM의 send() 를호출하게된다. 과정을보면먼저 initializefields() 를호출하여메시지내용을채우고 selectroute() 를호출하여다음에보내질 route path를결정한다. 다음에 message를 radio로보내게된다. initializefields() 와selectRoute() 는 MultiHopLEPSM에구현되어있다. - 58 -
initializefields() 에서는 originaddr 과 sourceaddr, hopcount 를넣는다. 이부분은이해하기좀힘든부분인데 차근차근보기바란다. 우선자기자신이 sink_node인경우와아닌경우가있는데, 이것은소스에서 gpcurrentparent->id!= TOS_UART_ADDR 를통해서판정한다. 그다음 sink_node가아니라면자신이보내는 surge_msg인지아니면다른노드가보내는걸중계해주는건지를구분해야한다. 이부분은소스의 (pmhmsg->sourceaddr == TOS_LOCAL_ADDRESS) && (pmhmsg->originaddr == TOS_LOCAL_ADDRESS) 를통해서판정한다. 이두가지를숙지하고소스를보기바란다. - 59 -
제일처음부분은네트워크형성이되지않아 parent에대한설정이없는경우에 surge_msg를보내는경우이다. Msg->addr = TOS_BCAST_ADDR 을설정하는걸볼수있다. [Msg->add] 은 message를보낼곳인데, 자신의부모가될것이다. 지금은없으므로 broadcasting 한다. 다음줄은 hopcount를비교해서 loop에빠져서계속돌고있는경우처리해주는부분이다. 다음부분은중요한데, 우선 flsduplicate에대해서알아보자. 자세한건밑에서설명하기로하고, 우선 flsduplicate은 bool 으로중복된메시지인지아닌지를나타낸다. 그런데헷갈리기쉬운건중복된메시지면이값이 TRUE가된다는것이다. 헷갈리지말자. 소스로다시가서보면 if( ){ else{ 이런형일것이다. If( 자신이만들어낸메시지이면 ) { flsduplicate = FALSE // 중복되지않았으므로 Else // 그외에는다른노드에서받은경우이므로 { updatenbrcounters(); // 중복된건지체크해서결과를 return 받는다. updatenbrcounters() 에대해서는조금있다가자세히다뤄보기로하자. 자그럼중복되었는지아닌지에대한판정이끝이났으므로이제 message를보내주기만하면된다. 그부분이바로젤마지막에있다. If(!flsDuplicate) 의의미는중복되지않았다는말이다. Message를채우고자신이 sink_node가아니면 sequence number를채우고, message를보낼곳 Msg->addr을자신의부모 id로넣는걸볼수있다. 중복되었을때는 Result = FAIL 로 set 하고 Result 를 return 한다. - 60 -
앞에서나온 updatenbrcounters() 를살펴보자. 앞에내용을다이해했다면이부분도쉽다. 우선 TinyOS는 message를중계해줄때해당 message를저장해놓는다. 거기가 NeighborTable이다. 그래서이곳에선지금받은 message와 NeighborTable에있는거랑비교해서같은게있는지확인해본다. 같은게들어왔다는말은 loop에빠져다시온거라고볼수밖에없을것이다. 이런놈은 routing 해주면안된다. 그래서그결과를중복되면 TRUE로중복아니면 FALSE로 return 해준다. 먼저 sdelta값을확실히알고넘어가야한다. sdelta 값은 sdelta = (seqno - NeighborTbl[iNbr].lastSeqno - 1); 이렇다. 여기도헷갈리기쉬운부분인데숙지해야할사항은 sdelta =0, >0, <0 이렇게세가지경우다. =0 : 정상적인다음 seqno를가진놈이들어왔다는거다. ( 마지막에 -1 해줬으므로 ) >0 : 중간에몇놈이가출했다.(error로인해잃어버렸든어쨌든, 그래도서비스는해줘야함 ) <0 : 더낮은 seq_number를가진놈이들어왔다는얘기다. 이건두가지경우로나뉜다. - 61 -
참고로 ACCEPTABLE_MISSED = -20 이다. 뒤에설명나온다. 여기서우선언급하고지나가겠다. If(flag 를통해서처음들어온이웃인지알수있다 ) { 처음들어왔으니방한칸마련해주자 Else if( 중복이아닌경우 ) { sdelta>=0 이므로우쨋든서비는해주자대신 missed 에가출여부를적어놓자 Else if(-20 보다작다는말은노드가초기화되었다고본다. 예를들어해당노드가 power off 했다가 on 하면초기화될것이다 ) { 해당노드에대한것들은우리도 reinitialize 해주자 Else( 위에없는경우 0>sDelta>=-20 이경우가해당할것이다 ) { -1 은바로전데이터의중복이다, 그외 -2 ~ -20 까지도중복이라고본다. 보내는건이걸로끝이다. 정리해보자 Surge_Msg를보내기위해 send() 호출하면 MultiHopEngineM.send() 가호출되고, 그안에서 initializefields() 에의해서 origin내용이채워지고, selectroute() 에의해다음보낼곳을정하게된다. 마지막으로 Radio로보내면끝이다. 지금부터는 Surge_Msg를받았을경우를알아보자. Surge_Msg를받으면발생하는 event는 MultiHopEngineM의 ReceiveMsg.receive() 이다. 위에서본 MultiHopLEPSM에있는 ReceiveMsg.receive() 랑은이름은같지만틀린놈이다. 우선 surge에서사용하는 message는두가지이다. 라우팅을위한 Route_Packet과실제센싱데이터를보내기위한 Surge_Msg 이다. MultiHopEngineM의 ReceiveMsg.receive() 는 Surge_Msg를받았을때처리하는곳이고, MultiHopLEPSM의 ReceiveMsg.receive() 는 Route_Packet을받았을때처리하는곳이다. - 62 -
자신에게온 message가맞는지확인하고 forwarding 시킨다. Intercept signal을날리는것은구현안되어있으므로의미없다. Forwarding의의미는예를들어3 -> 2 -> 1 -> 0 (sink_node) 로전송할때 3은 2로 forwarding하고 2는 1로 forwarding한다고한다. 그럼 mforward() 부분을살펴보자. If(FwdBufList가비어있는지체크하는부분이다. 이중버퍼가사용중이면 ) { 더이상진행하지않고그냥 return: forwarding 하지않는다 비어있으면다음으로넘어가보자 If(selectRoute() 를통해서경로를못찾으면 ) { 마찬가지로 forwarding 하지않는다. 경로찾았으면다음으로넘어가자 If(send() 에성공하면 ) - 63 -
{ 이제다음 send에사용해야하니 FwdBufList를 pnewbuf에넣자. 그리고내가보낸 message인 pmsg를 FwdBufList안에넣어두자. Send() 에실패하면결국 forwarding 하지않는다. return pnewbuf; MAC에서 ACK를받으면 senddone() event가발생한다. senddone() event에는 message의 ACK정보와 success 정보를가지고있다. 여기를보면실패했을때의구현이안되어있는것같다. 성공했을때는Forwarding Buffer를비워주는것으로끝난다. 6.5. Event Library 분석지금부터 TinyOs의 Library 중 Event Library를분석해본다. 먼저 "tinyos-1.x tos lib Events" 폴더를보면 "Event.nc", "EventM.nc" 두개의파일이존재함을확인할수있다. 두개의파일중 Event.nc 의내용을먼저확인해보자. - 64 -
내용을보면그리특별한것은없어보인다. Event가제공해주는인터페이스의종류 (StdControl, EventRegister, EventUse) 와컴포넌트들 (Commnad, EventM, LedsC) 과의연결을확인할수있다. 여기까지봤을때 Event 라이브러리의내용이EventM.nc 파일에거의모든것이구현되어있음을대충짐작할수있다. EventM.nc 파일을확인해보자. 소스가꽤길다. 처음부터차근차근접근해보도록하자. EventM이제공하는인터페이스와사용하는인터페이스를정의하는부분이다. 다음으로 implementation 부분을살펴보자. 먼저변수를정의하는부분이다. 변수를정의하는부분에서쉽게접하지못하는타입을볼수있다. EventDesc, EventDescs, ParamVals, EventQueue 타입들이어떤것들인지타입이정의된부분을찾아보자. 이중 EventDesc와 EventDescs 타입은 "tinyos-1.x tos interfaces" 폴더의 Evnet.h 파일에정의되어있다. - 65 -
EventDesc 타입은변수명에서대충짐작할수있듯이 Event의내용을정의하는구조체이다. 하지만각변수의정확한역할은아직모른다. 차츰 Event 라이브러리를분석해가면살펴보자. 구조체안의변수들중 ParamList 라는타입을가진변수가보이는데, 이타입은 "tinyos-1.x tos interfaces" 폴더의 Params.h 파일에다음과같이정의되어있다. 참고로 TOSType 타입은 "tinyos-1.x tos interfaces" 폴더의 SchemaType.h 파일에다음과같이정의되어있다. TOSType 타입은열거형이며, 0-10까지정의되어있음을확인할수있다. 다음은 EventDescs 타입을정의한부분이다. - 66 -
단순히 Event의개수와 EventDesc 타입의배열이구조체로정의되어있음을확인할수있다. ParamVals 타입은 "tinyos-1.x tos interfaces" 폴더의 Params.h 파일에정의되어있다. 파라미터의수를저장하는 uint8_t형의변수와파라미터data들의포인터 list들을저장하는 char형의포인터배열이구조체로정의되어있다. 마지막으로 EventQueue 타입을살펴보자. EventQueue 타입은 "tinyos-1.x tos interfaces" 폴더의 Evnet.h 파일에다음과같이정의되어있다. EventQueue는 EventInstance를저장하는 Queue를정의한타입임을확인할수있다. 그리고 EventInstance는 EventDesc의포인터를저장하는 EventDescPtr형의변수와 ParamVals형의변수로구성된구조체라는것도알수있다. 전역변수에대한설명이끝이났다. 이제부터본격적으로코드를처음부터하나씩차근차근살펴보도록하자. 변수설정다음으로나오는부분은 eventqueueinit() 함수이다. - 67 -
이함수는간단하게전역변수로설정한 eventqueue를초기화하는함수이다. 초보라도쉽게알수있는부분일것이다. 그밑으로는 eventenqueue() 함수가보인다. eventenqueue 함수는함수이름을보면짐작할수있듯이 eventqueue에 EventDescPtr 타입의데이터를집어넣는함수이다. 리턴타입으로 result_t 타입을반환한다. 처음으로현재 eventqueue의 size를검사하여큐에저장가능한장소가있다면계속진행하고, 저장할장소가없다면 FAIL 을반환하고끝낸다. 그리고현재 eventqueue의요소인 inuse의값이 TRUE이면큐에저장하지않고 FAIL 을반환하고끝낸다. 큐의유용성을체크하고난후, 우선 eventqueue의 inuse 요소의값을 TRUE로바꾼다. 이는큐에어떤이벤트를저장하고있는중에는다른이벤트를저장하지못하도록하여두개이상의프로세스가동시에이함수를호출했을때발생할수있는 error를미연에방지하기위해필요한부분이다. 다음으로는큐에데이터를저장하고 size를 1 증가시키는부분이다. 이부분은 C프로그래밍에서의원형큐에데이터를저장하는부분과다를바가없기때문에상세설명은생략하겠다. 데이터를모두저장하고나면 inuse를 FALSE로바꾸고 SUCCESS 를반환한다. - 68 -
위의코드는 eventdequeue 함수의모습이다. 이함수는현재 eventqueue에저장되어있는데이터를빼서가져오는함수이다. 리턴타입으로 result_t 타입을반환한다. 데이터를큐에서빼내오는역할을하는함수이기때문에가장먼저큐에데이터가있는지체크 (if (eventqueue.size == 0)) 를한다. 큐에꺼내올데이터가없다면 FAIL 을반환하고끝낸다. 이함수역시두개이상의프로세스가이함수를호출했을때발생할수있는 error를미연에방지하기위해 eventqueue의 inuse 요소를이용한다. 그리고큐의 head에있는 eventinstance 형의데이터를함수의인자인 *eventdesc와 *params에저장한다. 다음으로는큐의사이즈를줄이고 head값을하나더하는부분인데, 이는 C 프로그래밍에서원형큐를구현하는것과같으므로상세설명은생략하겠다. - 69 -
위코드는 StdControl command 를구현해놓은부분이다. StdControl.init() 에서는 eventdescs를비롯한전역변수들의초기값을저장한다. StdControl.start() 와 StdControl.stop() 는단순히 SUCCESS 를반환한다. 다음은 EventUse.getEvent command 이다 EventUse.getEvent() 는 char 형의 name을인자로받는다. 그리고 for 문을이용하여현재 eventdescs에저장되어있는 EventDesc 형의데이터수만큼반복하면서인자로받은 name과 EventDesc 형의데이터의 name 요소와값을비교하여같은값이면그데이터를반환하는 command이다. 1 은 EventDesc 타입의 deleted 요소값이 FALSE 인지확인하는부 - 70 -
분으로이벤트가이미지워진이벤트인지검사하는부분이다. 2 는 strcasecmp 함수를이용하여인자로받은 name의값과이벤트의 name과비교하여같은지를검사하는부분이다. < 참고 > - strcacsamp #include <string.h> int strcasecmp(const char *s1, const char *s2); 대소문자를구분하지않고, 두문자열 s1과 s2를비교한다. 반환값 : s1과 s2가같으면 0, s1이 s2보다크면 0보다큰정수 s1이 s2보다작다면 0보다작은정수를반환한다. 위코드는 EventUse.getEventByID command를구현한부분이다. uint8_t 형식의 id를인자로받아서 eventdescs에저장되어있는이벤트중 id와같은인덱스에위치하는이벤트를반환하는 command 이다. 만약 id가유효하지않다면 NULL을반환한다. 1 은인자로받은 id 가유효한지검사하는부분이다. 다음으로나오는 EventUse.getEvents 와 EventUse.numEvents command 는아주간단하다. EventUse.getEvents() command는전역변수인 eventdescs의주소를반환한다. 이것은현재저장되어있는 EventDesc 형의데이터들을모두 - 71 -
반환한다는뜻임을알수있을것이다. EventUse.numEvents() command는 eventdescs의 numevents 요소의값을반환한다. 이것은현재저장되어있는 EventDesc 형의데이터들이몇개인지를반환한다는뜻임을쉽게눈치챌수있을것이다. 위코드는 signaleventtask() task를구현한부분이다. signaleventtask() 는이벤트를실질적으로처리하는 Task이다. 우선현재처리해야할이벤트 (currenteventdesc) 가있는지체크하는부분이 1 부분이다. 만약처리해야할이벤트가있으면 char 형의 name 변수에현재이벤트의 name을입력하고, 2부분에서보는바와같이 CommandUse.InvokeById() - 72 -
를호출하여 currenteventdesc 의 cmds[] 요소에저장되어있는 command 를처리한다. 여기서잠깐, CommandUse.InvokeById() 를살펴볼필요가있을것같다. CommandUse.InvokeById() 는 tinyos-1.x tos lib Commands Command.nc 에정의되어있다. 소스를살펴보면, 우선 commanddesc 변수에 CommandUse.getCommandById() 를호출하여 CommandDescPtr 타입의데이터를저장하고이변수의값이 NULL이거나 commanddesc의 params.numparams 값이랑인자로받은 params의 numparams의값이다르면 FAIL을반환한다. 밑의코드는 CommandUse.getCommandById의소스부분이다. 그리고 Cmd.commandFunc event를호출한다. 이때인자는 commanddesc 의 name요소, resultbuf, errorno, params 이며, 반환된값이 SUCCESS가아니면 FAIL을 return한다. 밑의소스는 Cmd.commandFunc() event이다. 또한 errorno의값이 SCHEMA_ERROR이면 FAIL을 return한다. errorno의타입인 SchemaErrorNo는 SchemaType.h에다음과같이정의되어있다. - 73 -
이쯤에서 CommandUse.InvokeById() 의설명을끝내고계속해서 signaleventtask() 를살펴보자. CommandUse.InvokeById() 를호출하여 command를처리하고그결과값이 SUCCESS가아니거나 errno의값이 SCHEMA_ERROR 일경우 currenteventdesc의값을 NULL로바꾸고 EventUse.eventDone event를 name과 SCHEMA_ERROR를인자로하여호출한다. 만약 command를처리한결과값이앞서설명한경우가아니라면 3 부분과같이다시한번 errno의값을체크하는데그값이 SCHEMA_RESULT_PENDING 이면 currentcmddone의값을 FALSE로준다. 이부분은 command를처리하고결과가확실하게나지않았을경우현재이벤트를생략하고다음이벤트를처리하기위한작업인것같다. command를정상적으로처리하였을때 nextcmdidx를 1증가시키고혹시, 이값이유효한지체크하는부분이 4 이다. currenteventdesc의 numcmds의값과비교하여크면현재이벤트에포함된 command를모두처리한것이기때문에 currenteventdesc의값을 NULL로바꾸고 EventUse.eventDone event를호출한다. 이때인자는 name과 SCHEMA_SUCCESS 값이다. 5 에서는 currenteventdesc가 NULL인지를체크하는부분인데만약 NULL이면 6 에서와같이 eventdequeue 함수를호출하여 eventqueue 에저장되어있는 EventDesc 타입의데이터를가져온다. 이때데이터를가져오지못하고 FAIL이반환된다면 eventqueue에저장된이벤트가없다는뜻이므로 signaleventtask를모두끝낸다. 7 은 eventqueue에서가져온데이터를이용하여전역변수인 currenteventdesc, currentparams, currentcmddone, nextcmdidx의값들을설정하는부분이다. 마지막으로 8 은남은현재이벤트의 command를처리하거나다음이벤트를처리하기위하여 signaleventtask() 를호출하는부분이다. 다음으로구현되어있는 EventUse.signalEvent() command를보자. - 74 -
EventUse.signalEvent() 는 EventUse.getEvent() 를호출하여 EventDescPtr 타입의데이터를가져온후이데이터의유효성을체크하고, eventenqueue() 함수를이용하여 eventqueue에저장하는 command이다. 그리어렵지않아서자세한설명은하지않아도될듯하다. 이어서 EventUse.signalEventMsg() command 를분석해보자. EventUse.signalEventMsg() command는어떤역할을하는함수인지정확히파악하지못하였다. 짐작하건대 TOS_Msg를인자로받아서이메시지를이벤트의이름, 파라미터로 parsing을하고이이벤트를 signal 하기위한 command인듯하다. 우선 1 에서 eventmsgpending값을검사하여 TRUE이면 FAIL을 return 하고끝낸다. FALSE이면 2 가실행되어지는데 TOS_Msg를 parsing 하기위한초기작업을하는부분이다. msgcopy의값을인자로받은 TOS_MsgPtr 타입의데이터가가리키는값으로입력하고, cmsg의값을 msgcopy의 data요소를 struct CommandMsg* 형태로타입변환하여입력한다. 아래소스는 TOS_Msg와 struct CommnadMsg를정의한부분이다. - 75 -
3 에서 cmsg의 nodeid 요소값이 TOS_BCAST_ADDR 이거나 TOS_LOCAL_ADDRESS 인지를체크한다. 체크한결과가 TRUE이면 4에서보는바와같이 cmsg의 data[0] 의주소를 char * 형으로변환하여 ptr에저장하고이값을 char * 타입의 eventname 로컬변수에저장한다. 그리고 eventname을이용하여 EventUse.getEvent() 를호출하여반환된 EventDesc 타입의데이터를 eventdesc 로컬변수에저장한다. 5의 for문에서는 eventdesc의 params.numparams의값만큼반복하면서 TOS_Msg에서파라미터의값에해당하는데이터를 ParamVals.paramDataPtr[i] 에저장한다. 6에서는파라미터의값들을모두저장하고난후, paramvals.numparams 의값을 eventdesc의 params.numparams 값으로저장하고 eventname과 ¶mvals를인자로한 EvnetUse.signalEvent() command를호출하여 parsing한이벤트를이벤트큐에저장한다. - 76 -
위의코드는 EventUse.registerEventCallback() command 이다. 이 command 는 char * 형의 eventname 과 cmdname 을인자로받아서 EventDesc 타입의 command 요소를구성하는작업을한다. 1은인자로받은 eventname을이용해 EventUse.getEvent() 를호출하여반환된값을 eventdesc 로컬변수에저장하는부분이다. 다음으로 2에서는 1에서반환받은 eventdesc가 NULL 이거나 eventdesc의 numcmds 요소의값이 MAX_CMD_PER_EVENT 보다크다면 FAIL을반환하고끝낸다. 3에서는인자로받은 cmdname을이용해 CommandUse.getCommand() 을호출하여반환된값을 cmddesc 로컬변수에저장한다. 이렇게받은 cmddesc가 NULL인지를체크하고 NULL이면 FAIL을반환하고끝내는부분이 4이다. 이벤트의파라미터개수와 Command의파라미터개수는같아야하고만약다르면 FAIL을반환하고끝내야한다. 이작업을처리하는부분이 5이다. 다음으로 for문을이용하여이벤트의파라미터들과 Command의파라미터들이각각같은 TOSType 인지를체크한다. 이부분이 6이다. 유효성체크가모두끝이나면마지막으로 7에서와같이 eventdesc의 cmds요소에 cmddesc의 idx요소를저장하고 eventdesc의 numcmds요소를 1증가시킨다. - 77 -
다음으로살펴볼것은 EventUse.deleteEventCallback() command이다. 이 command는 char * 타입의 eventname과 cmdname을인자로받아서 eventname과같은이벤트를찾고그이벤트를구성하고있는 command 중 cmdname과같은 command를 delete한다. 1은인자로받은 eventname을이용하여이벤트를찾는부분이다. eventdesc 로컬변수에 EventUse.getEvent(eventName) 을호출하여받은데이터를저장하고, eventdesc의값이 NULL 이면 FAIL을 return하고끝낸다. 2부분은인자로받은 cmdname을이용하여 command를찾고그 command의값이 NULL 인지체크한다. cmddesc 로컬변수에 CommandUse.getCommand(cmdNAme) 을호출하고 return받은데이터를저장한다. 그후 cmddesc의값이 NULL이면 FAIL을 return하고끝낸다. 3은 for문을이용하여 eventdesc의 numcmds요소의값만큼반복하면서 eventdesc의 cmds요소값들중 cmddesc의 idx요소의값과같은것이있는지체크하고있으면 for문을빠져나온다. 이것은현재이벤트를구성하고있는 command 중지우려고하는 command의 index 를찾기위한작업이다. 4는 for문을빠져나왔을때 cmd 값이 eventdesc의 numcmds의값보다큰지체크하고크면 FAIL을 return하고끝내는부분이다. 이것은 for문을모두반복하고나왔을때 eventdesc의 cmds 요소값들중지우려는 command가없을경우를체크하기위한로직이다. 5 는 for 문을이용하여 eventdesc 의 cmds 배열을다시재구성하는부 - 78 -
분이다. 만약지우려고하는 index보다뒤에있는데이터들을앞으로이동시켜 cmds배열을재구성한다. 마지막으로 6에서와같이 eventdesc의 numcmds요소의값을 1감소시키고 SUCCESS를 return한다. 계속해서 EventRegister.registerEvent() command를살펴보도록하겠다. EventRegister.registerEvent() command는 char* 형의 evnetname과 ParamList * 형의 params를인자로받아서 eventdescs에이벤트를등록하는 command이다. 1에서인자로받은 params의 numparams의값이 MAX_PARAMS의값보다큰지체크하고만약크면 FAIL을 return하고끝낸다. 2에서는 addeventpending의값과 eventname의 length가 MAX_EVENT_NAME_LEN 보다큰지를체크한다. 만약 addeventpeding - 79 -
의값이 TRUE이거나 eventname의 length가 MAX_EVENT_NAME_LEN 보다크면 FAIL을 return하고끝낸다. 그렇지않다면 addeventpending 의값을 TRUE로바꾸고계속해서작업을진행한다. addeventpending 로컬변수는두개이상의프로세스가동시에이작업을호출하였을경우발생할수있는 error를방지하기위한 bool형변수이다. 3에서는 for문을이용하여전역변수인 eventdescs에저장되어있는이벤트중지워진이벤트가있는지, 또는저장하려는이벤트의이름과같은이름을가지고있는지검사한다. 4부분이 eventdescs.eventdesc[eventidx].deleted 의값을체크하여현재 eventdescs에저장되어있는이벤트중 deleted 된이벤트가있는지검사하는부분이다. 만약 eventdesc[] 중간에 deleted 된이벤트가있다면그곳에새로운이벤트를등록해야하므로로컬변수인 eventdesc에배열의 deleted 된장소의주소값을저장한다. 5부분이 eventdescs.eventdesc[eventidx].name과인자로받은 eventname 값을비교하여저장하려는이벤트의이름이이미저장되어있는지를체크한다. 만약같은이름을쓰는이벤트가저장되어있다면 eventexists의값을 TRUE로바꾸고 for문을빠져나온다. 6의 if 문은 eventexists의값을체크한다. 만약 eventexists의값이 FALSE이면이벤트가존재하지않는것이므로, 새로운이벤트를저장하는것이된다. 그래서 eventdescs.numevents의값을 1증가시켜준다. 만약 eventdescs.numevents의값이 MAX_EVENTS 보다크거나같으면 addeventpending의값을 FALSE로바꾸고 FAIL을 return한다. 7에서는 3의 for문내에서 eventdesc의값을저장하지못하였을경우 eventdesc를지정해준다. 마지막으로 8에서이벤트의값들을모두 setting한다. 그리고 SUCCESS를 return한다. - 80 -
EventRegister.deleteEvent() command는 eventdescs에등록되어있는이벤트중인자로받은 name과같은이름을쓰는이벤트를 delete하는작업을한다. 1에서보는바와같이 for 문을이용하여 eventdescs에등록된이벤트의수만큼반복하면서 2의조건문에서 deleted의값이 FALSE이면서인자로받은 name 값과이벤트의값이같은지를체크하고조건을만족하면 eventdesc[i] 의 deleted의값을 TRUE로바꾸고 SUCCESS를 return한다. for 문이끝나도록이벤트를지우지못한경우에는 FAIL을 return 한다. 마지막으로살펴볼것은 CommandUse.commandDone() event 이다. CommandUse.commandDone() event는 command가처리되었을때발생하는 event이다. 1의조건문은 currentcmddone가 TRUE이거나 currenteventdesc가 NULL 일때 SUCCESS를 return하는데, currentcmddone과 currenteventdesc의초기값이각각 TRUE와 NULL이므로이조건은어떤이벤트의 command 가처리되지않았을때를말한다. 그래서아무런작업도하지않고단순 - 81 -
히 SUCCESS를 return하는것이다. 2에서는먼저인자로받은 errorno의값을체크하는데 errorno의값이 SCHEMA_ERROR일때, currenteventdesc와 currentcmddone의값을초기화시키고 EventUse.eventDone() event를호출한다. 이때인자는 name과 SCHEMA_ERROR이다. 즉, 이벤트가제대로처리되지않고 error가발생했다는것이다. 3은 command가제대로처리되었을때동작을하게되고, command는처리가제대로되었기때문에 currentcmddone의값을 TRUE로초기화시켜준다. 4의조건문에서는 currenteventdesc에처리할 command가남아있는지체크한다. 만약모든 command가처리되었다면 currenteventdesc 의값을초기화시키고 EventUse.eventDone() event를호출한다. 이때인자는 name과 SCHEMA_SUCCESS이다. 아직처리할 command가남아있다면현재이벤트가모두처리된것이아니기때문에마지막에 SUCCESS만 return한다. - 82 -
6.6. MultihopRouter & MultiHopEngineM Component 6.6.1. Interfaces 위의그래프를통해 MultihopRouter는아래와같은 6개의인터페이스를제공하고 1개의외부인터페이스를사용하는것을확인할수있다. 제공인터페이스 - StdControl, Receive, Send, Intercept, Intercept as Snoop, RouteControl 사용인터페이스 - ReceiveMsg 그리고이것은 MultihopRouter.nc 파일을통해서도확인할수있다 - 83 -
CHECK - Component Graph 의화살표 TinyOS 의 Component Graph 에서각 Component 사이의화살표중에서점선화살표와실선화살표의의미는다음과같다 - 점선 : (=) Equal 을의미한다. 다시말해서화살표가시작하는컴포넌트의 Interface 는직접적으로하는일이없고화살표가끝나는컴포넌트의 Interface 가해당하는작업을수행한다. 위의 MultihopRouter 의경우 MultihopRouter 의 Interface 대신 MultihopEngineM 의인터페이스가작업을수행하게된다. 실제구현부의소스를확인해보면다음과같다 - 실선 : (->) 연결, 호출을의미한다. 다시말해서화살표가시작하는컴포넌트의 Interface 가호출되면화살표가끝나는컴포넌트의인터페이스가자동으로호출되는것을의미한다. 위의 MultihopRouter 의경우 MultihopRouter 를제외한나머지컴포넌트들은이와같이연결되어있음을확인할수있다. 실제구현부의소스를확인해보면다음과같다. 6.6.2. Multihop Message(MultiHop.h) Multihop Message의구조를확인하기위해 TOS_Msg의구조와함께살펴보면 - 84 -
위와같은구조체로구성되어있는것을확인할수있고, TOS_Msg 중에서 data에해당하는부분에 Multihop Msg가실려서전송되는구조를가지고있다. Multihop Msg의구조체는아래와같이구성되어있다. 6.6.3. MultiHopEngineM의 Initialize 위의그래프에서보았듯이 MultihopRouter에서제공되는인터페이스는 MultiHopEngineM에모두구현되어있다. 따라서 MultiHopEngineM.nc 의소스를살펴보도록하겠다. 위의소스는 MultiHopEngineM.nc 파일의시작부분이다. TOS_Msg가정의되어있었던 AM.h 와 MultiHopMsg가구현되어있던 MultiHop.h가 include 되어있는것을볼수있다. MultiHopEngineM에서제공되는 interface와사용되는외부인터페이스의목록도확인할수있다. - 85 -
다음으로 inplementation 부분을살펴보자. 위의소스는 implementation중에서초기화를하는부분이다. 우선내부함수로 initialize() 가구현되어있는데이함수에서는 FWD_QUEUE_SIZE에해당하는개수만큼 FwdBufList를생성한후 ifwnbufhead 와 ifwdbuftail의값을 0으로초기화해준다. 그리고 StdContorl의 init() 에서 initialize() 가호출되는것을확인할수있다. 그리고 CommStdControl.init() 을호출하고 SubControil.init() 을호출하여 return 하는것을확인할수있다. 이부분을자세히살펴보면 MultiHopEngineM의 StdControl - 86 -
이초기화되면서동시에수행되는일을확인할수있는데. 그림 4를참조해보면, CommStdControl은곧 StdControl을말하는것이고이것이초기화되면 Comm의 StdControl도함께초기화하도록 Wiring 되어있는것을알수있다. 여기서 Comm은 GenericCommPromiscuous의별칭이다. 그리고그후에 SubControl을초기화하는데예전코드를참조하여보면 QueuedSend와 MultiHopLEPSM의 StdControl이초기화되도록 Wiring 되어있음을알수있다. 여기서왜 GenericCommPromiscuous의 StdControl은 SubControl 을이용해서 Wiring 하지않고별도의별칭을사용해서따로 Wiring 하였는지의문점이생기지만뒤에다른구현을편리하기위해특별히그렇게했을것이라는짐작만하고일단넘어가겠다. 다음으로 StdControl.start() 를살펴보면 CommStdControl과 SubControl의 start() 를수행해서 QueuedSend와 MultiHopEngineM의 StdControl의 start() 를수행한다는것은앞서서살펴본 init() 을통해쉽게파악할수있을것이다. 그리고마지막으로 CommControl.setPromiscuous(TRUE) 를호출하는것을볼수있는데 CommControl은 GenericCommPromiscuous 에서제공되는 interface라는것을그림 1의그래프를통해알수있다. 그러면 GenericCommPromiscuous의소스를직접살펴보겠다. - 87 -
위의소스를살펴보면 CommControl이라는인터페이스는결국 AMPromiscuous에있는 CommControl과 = 로연결되어있는것을확인할수있다. 그렇다면이것을 GenericCommPromiscuous의컴포넌트그래프를통해확인을해보자. 그림 Ⅱ-6-11. GenericCommPromiscuous Component Graph 위의그래프를통해서도 GenericCommPromiscuous의 CommControl을비롯한몇몇인터페이스들이 AMPromiscuous 의그것들과점선으로연결되어있는것을확인할수있다. 결국, CommControl.setPromiscuous(TRUE) 은 AMPromiscuous의 CommControl 인터페이스에구현되어있는 setpromiscuous() 를호출하는것임을알게된다. setpromiscuous command의내부를보면단순히매개변수로넘어온 bool 값을 promiscuous_mode의값으로세팅하는것을알수있는데이값을세팅하는이유는다시따로다루도록하겠다. 우선여기서는이러한 Wiring을통해결국 AMPromisecuous까지오게되는과정을이해하는것으로만족하도록하자. 다시 MultiHopEngineM으로돌아가서마지막으로 StdControl.stop() 을살펴보면이 command는특별히특이한사항은없고 init() 에서초기화했던것과반대로 CommStdControl 과 SubControl을 stop() 하고종료하도록되어있다. - 88 -
CHECK - GenericComm 과 promiscuous_mode TinyOS에서는다수의 mote가통신을하면서발생할수있는다양한간섭을피하고필요에의해 mote의그룹을구성할수있게하기위해다양한변수에의한필터링을지원한다. 필터링할수있는요소로는 address(moteid), group, AM_type 등이있다. 우리가다수의 mote들로부터데이터를받아서처리하기위해흔히사용하는 TOSBase는 address와 AM_type에대해서는필터링을지원하지않고오로지 group에의한필터링만지원한다. 그리고보통일반적인 mote application들은전송받은데이터를처리하기위해 GenericComm을사용하는데이 GenericComm 컴포넌트의경우 AMStandard 컴퍼넌트를사용하는데여기서제공하는 receive() 는 group ID, node ID, AM_MSG ID 모두를필터링하게되어있다. 하지만 MultiHopEngineM 에서사용하는 GenericCommPromiscuous 컴퍼넌트의경우에는 AMPromiscuous 컴퍼넌트를사용하는데이컴퍼넌트의 receive() 는기본적으로 node ID와 group ID를체크하긴하지만 node ID 는선택적으로검사하지않게할수있는데이때필요한설정변수가 promiscuous_mode 이다 위소스에서 promiscuous_mode가 TRUE일경우 node ID에대한필터링을하지않게된다. 그리고이 promiscuous_mode를세팅할수있게해주는 command가위의앞선본문에서알아본commcontrol.setpromiscuous(true) 이다. - 89 -
6.6.4. MultiHopEngineM의 commands 6.6.4.1. Send 1 2 3 4 그림 15. MultiHopEngineM의 Send.send command 현재 Chapter에서설명하고있는 MultiHopEngineM의 command들은실제메카니즘을설명하기보다는전체적으로어떠한순서와구조로돌아가고있는지를개략적으로살펴보도록하겠다. 결국은각자의역할에맞는컴포넌트로분기호출되어각자상세한일을처리하기때문에그에해당하는상세한설명은해당컴포넌트에관한 Chapter에서다시상세히다루도록하겠다. 1번을살펴보면먼저 TOS_MHopMsg의 data 사이즈와 PayloadLen 사이즈를더한전체메시지길이를구해서 TOSH_DATA_LENGTH 사이즈보다큰지체크하고만약크다면 FAIL을반환하는부분이다. 여기서 TOSH_DATA_LENGTH는 TOS_Msg에서 data 부분의사이즈로서 AM.h에보면 29로정의되어있고이사이즈는곧 Multihop msg의사이즈이다. 1번에서 FAIL되지않으면그다음 2번의 RouteSelect.initializeFields를수행하는데 RouteSelect 인터페이스는 MultiHopLEPSM에서제공된다. initializefields를비롯한 MultiHopLEPSM에서제공되는 command들은나중에별도의 chapter를통해자세히알아보도록하고여기서는특정한 id에해당하는 MultiHop Msg의 data와 sourceaddr, hopcount 등의필드값을초기화하는역할을한다는정도만알아두도록하자. 2번이성공하면이제 3번에해당하는 RouteSelect의 SelectRoute command를실행하는데이는 2번에서초기화한 MultiHop msg 의 Route를선택하는역할을한다. 실제로이 command의역할이 MultiHop Routing에있어서가장중요한부분인데이것도역시이 - 90 -
정도만알아두고별도의 Chapter를통해자세히알아보도록하자. 3번까지의과정이정상적으로완료되면이제실제로 MultiHop Msg를전송하게되는데그부분이바로 4번부분이다. SendMsg는그림 1번의그래프에서보이는 QueuedSend 컴포넌트에서제공되는인터페이스로서 Message Queue에들어온메시지를실제로전송해주는역할을하는인터페이스이다. 소스에서보는바와같이매개변수로 msg의전달주소와메시지의길이, 그리고메시지객체를전달하는것을볼수있다. SendMsg 인터페이스에대한자세한설명도별도의 QueueSend 컴포넌트에관한 Chapter에서다루도록하겠다. 모든과정이완전히성공하면 SUCCESS 를반환한다. 위의 getbuffer command는단순히해당하는 MultiHop Msg의길이를구하여반환해주는 command이다. 6.6.5. ReceiveMsg.receive event 1 2 그림 17. MultiHopEngineM의 ReceiveMsg.receive event receive 이벤트는말그대로데이터가 node에도착하였을때발생하는이벤트이다. 이벤트가발생하면 TOS_Msg의포인터가전달되는데이포인터를통해해당메시지의 data와 length 등에접근하게된다. - 91 -
Ⅱ. TinyOS Operation 1. TinyOS 란? UC 버클리에서진행해온스마트더스트 (Smart Dust) 프로젝트에사용하기위하여개발된컴포넌트기반내장형운영체제 (OS). 네트워크내장형시스템을위해특별히디자인된초소형 open-source OS이다. 핵심 OS 코드는 4000바이트이하이고, 데이터메모리는 256바이트이하이며, 이벤트기반멀티태스킹을지원한다. 센싱노드와같은초저전력, 초소형, 저가의노드에저전력, 적은코드사이즈, 최소한의하드웨어리소스를사용하는내장형 OS를목표로하며, 내장형네트워크를위한프로그래밍언어로는 nesc가사용된다. Radio 통신을위해서 IEEE 802.15.4를기반으로하고있다. 그림 Ⅱ-1-1 그림에서보는것과같이 IEEE802.15.4 규격을사용하면서 Radio 통신을위해서 CC2420칩을사용하고있는것을볼수있다. CC2420칩을이용한여러가지 Radio 부분의설정을 CC2420RadioM.nc 파일을통해가능하다. - 92 -
1.1. 디렉토리구조 TinyOS는 Cygwin의 'opt' directory안에 tinyos-1.x라는이름을가지고위치하고있다. 그림 Ⅱ-1-2 TinyOS 디렉토리구조 tinyos-1.x 하부디렉터리들을좀더살펴보겠다. 그림 Ⅱ-1-3 하부디렉토리 - apps' : 기본적인 TinyOS application 포함 - contrib' : user( 관련회사나단체 ) 들이제공한 application 포함. - doc' : tutorial 및여러가지문서들. - tools' : utilities & java application - 'tos' : 하드웨어관련 module 및 interface - 93 -
['tos' directory 내부 ] 그림 Ⅱ-1-4 tos 디렉터리구조 - interface' : TinyOS component를위한interface모음 - lib' : library 모음 - platform' : platform(hardware) 의존적인 component - 'sensorboards' : sensorboard(hardware) 의존적인 component - 'system' : 기본적인하드웨어및 kernel 관련 component - 'types' : type 정의 ( 예, AM.h) 1.2. TinyOS Layers 그림 Ⅱ-1-5 TinyOS Layers mote에다운로드되는하나의 application에는 TinyOS kernel이그 application의 scheduler 역할과 hardware 초기화역할을수행하게된다. 기존 embedded system과의차이점이라면, OS가탑재되고 OS 상에서여러가지 application이실행되는반면, TinyOS는하나의 application만을동작시킬수있다는것이다. - 94 -
1.3. TinyOS Component Hierarchy 그림 Ⅱ-1-6 TinyOS Components 의 Hierarchy 위의그림은 TinyOS의 Component들의 Hierarchy들을잘보여주고있다. 제일왼쪽편에서 Radio 통신을위한 component들의계층도를볼수있다. UART는 PC와 serial 통신을위한 component이다. ADC는 sensing 된 analogue값을 digital로변환하여준다. 2. TinyOS 설치하기설치에앞서 TinyOS 설치프로그램을다운받는다. 본문서에서다운받는 TinyOS는 window용으로하나의설치파일로되어있으며, 설치하면 Cygwin, TinyOS, JDK 1.4 & JavaCOMM package, GraphViz 등이설치된다. 2.1. TinyOS 다운받기 http://www.tinyos.net/dist-1.1.0/tinyos/windows/ 주소로들어가면아래와같은화면이나온다. 그림 Ⅱ-2-1 다운로드화면 여기서 tinyos-1.1.11-3is.exe( 노란색부분 ) 을다운받는다. 다운이끝나면, 다운받은폴더에 tinyos-1.1.11-3is.exe 파일이생성된다. - 95 -
2.2. 설치과정 파일을실행할것인지묻는창이뜨고, 실행을클릭 그림 Ⅱ-2-2 파일열기 window 설치 type 선택과설치할폴더를지정해준다. 기본적으로 complete type과 C: Program Files UCB 로정해져있다. 선택하였다면 [ 다음 ] 을클릭 그림 Ⅱ-2-3 InstallShield Java License에대한동의를하고설치 setting에대한확인을하면설치가시작된다. 설치에필요한파일들의 copy가끝나면 Cygwin 설치를위해 [Continue] 를클릭. - 96 -
Cygwin 이설치되고다음과같은창이뜨면서설치가끝났다. 그림 Ⅱ-2-4 설치완료 platform에따라서따로설치해야하는장치 Driver가있을수도있으니참고하기바란다. 2.3. What's Being Installed? 그림 Ⅱ-2-5 Install 후설치되는요소들 처음 TinyOS를설치하게되면위와같은것들이설치된다. TinyOS가리눅스환경에서작동하기때문에 Cygwin을설치한다. 윈도우에서리눅스와비슷한환경을제공해주는프로그램이다. 그리고 PC application들이 Java로작성되어있기때문에 Java 1.4 JDK를설치한다. - 97 -
3. TinyOS application 시작하기 3.1. application 설치하기 3.1.1. compile 그림 Ⅱ-3-1 Compile 과정 compile과정은 nesc로구성된 application을 nesc compiler로 compile하면 app.c가생성되고이를 C compiler(avr, msp,...) 로 compile하면최종으로원하는 object file(exe, hex,...) 이생성되게된다. 이와같은일련의과정이 make를통해이루어진다. $> make <platform> 이렇게명령을내리게되면 application을 platform 별로 compile 해준다. 3.1.2. application을 mote에다운로드이제 compile된 image를 mote에다운로드를해야한다. ($> make install <platform>) : 이명령은 platform 별로 compile 한후 mote에다운로드한다. ($> make reinstall <platform>) : 이명령은이미 compile되어있는 image를 mote에다운로드한다. ($> make install.<#> <platform>) : 이명령은 <#> 자리에숫자를넣음으로써, mote에 ID를부여해준다. (reinstall도동일.) < 참고 > - $> make install,1 <platform> 위의명령은 1번 ID를가진 mote로 application을다운로드하라는명령이다. 3.1.3. application의 doc ($> make docs <platform>) 위명령은 application을 platform 별로 document를만든다. html 형태이며, 생성되는위치는 make 할때첫줄에표시되어있는데통상적으로 C: Program Files UCB cygwin opt tinyos-1.x doc nesdoc <platform> 위치에생성된다. - 98 -
3.1.4. TOS Message Packet Structure Mote간의정보는 TOS Message의형태로전달되게된다. 아래그림은 TOS Message의구성을보여준다. 그림 Ⅱ-3-2 TOS Message 구성 Payload의길이, 순서번호, PAN ID등이나와있고, Payload 부분에사용자가원하는정보가들어가게된다. 3.2. Blink application Blink는일정시간주기마다 led 하나가켜졌다꺼졌다를반복하는간단한 application이다. 우선 mote를 PC와연결한다. ( 인터페이스에따라서 usb 또는 serial) Blink 디렉터리로이동 ($>cd /opt/tinyos-1.x/apps/blink) Blink application을 compile하고 mote에다운로드 ($>make install <platform>) 하면아래와같이진행됨을확인할수있다. 그림 Ⅱ-3-3 mote 에 application 을다운로드한모습 - 99 -
필자는 micaz를사용했는데따로 serial을통하여다운로드를해야하기때문에 compile만수행하였다. 이와같은경우가아니라면 compile과다운로드를동시에수행할수있다. 프로그램다운로드가끝나면 red led가일정주기마다깜박이는것을볼수있다. 3.3. CntToLedsAndRfm & RfmToLeds CntToLedsAndRfm은숫자를 0~7까지카운트하고이를 3개의 led로표시하며동시에 RF로전송해주는 application이다. RfmToLeds는 RF를통해들어오는숫자를 3개의 led를통해표시하는 application이다. 따라서하나의 mote에는 CntToLedsAndRfm을, 또다른 mote에는 RfmToLeds를다운로드한다. 하나의 mote를 PC와연결하고 CntToLedsAndRfm application을다운로드한다. ($>cd /opt/tinyos-1.x/apps/cnttoledsandrfm) ($>make install <platform>) 또다른 mote에 RfmToLeds를다운로드한다. ($>cd /opt/tinyos-1.x/apps/rfmtoleds) ($>make install <platform>) 실행. 프로그램다운로드가모두끝나면 CntLedsAndRfm가다운로드된 mote를동작시키면 RfmToLeds도동일하게동작하고 CntLedsAndRfm 이동작을중지하면 RfmToLeds도중지하는것을볼수있다. 3.4. OscilloscopeRF OscilloscopeRF는 총 2개의 application으로 이루어진다. 하나는 TOSBase로 RF를통해 Packet을받아 PC로넘겨주는역할을하게되 며, 다른하나인 OscilloscopeRF는센서를통해서센싱을하고이값 을 RF로보내주는역할을하게된다. 동작의결과는 Java application 을통해볼수있다. OscilloscopeRF 다운로드하기. Mote 하나를 PC와연결한다. OscilloscopeRF 디렉터리로이동 ($>cd /opt/tinyos-1.x/apps/oscilloscoperf) OscilloscopeRF application을 compile하고 Mote에다운로드 ($>make install <platform>) - 100 -
TOSBase 다운로드하기. 이번에는 RF로보낸센싱값을받아들여서 PC로전달해주는 TOSBase를 Mote에다운로드한다. 또다른하나의 Mote를 PC와연결한다. TOSBase 디렉터리로이동 ($>cd /opt/tinyos-1.x/apps/tosbase) compile하고다운로드한다. ($>make install <platform>) 실행. 프로그램의다운로드가모두끝났다면, TOSBase를 PC와연결하고, OscilloscopeRF가설치된 Mote의전원을켠다. OscilloscopeRF가다운로드된 Mote가센싱된정보를 RF를통해서보낼때마다, LED가반짝거리는걸볼수있다면, 정상적으로작동하는것이다. 마찬가지로 TOSBase가다운로드된 Mote도 RF로정보를받을때마다 LED가깜빡거리는걸볼수있다. 3.5. PC application 이제위의단계까지완료가되었지만, PC에서직접눈으로확인할수가없어서불편할것이다. PC쪽의 application을실행시켜보자. 3.5.1. SerialForwarder SerialForwarder는 COM port로받은데이터를 IP로전달해주는역할과서버역할을한다. java 디렉터리로이동.($>cd /opt/tinyos-1.x/tools/java) SerialForwarder을실행. ($>java net.tinyos.sf.serialforwarder -comm serial@com7:<platform>&) -comm 은자신의 PC 에맞는 serial port 를설정하게해준다. 위의예에선 7 번 port 로 TOSBase 가연결되어있다고가정하였다. 마지막의 & 은 SerialForwarder application을백그라운드프로그램으로실행시키라는의미이다. 리눅스명령이므로자세한설명은생략. - 101 -
실행화면 그림 Ⅱ-3-4 SerialForwarder 실행화면 좌측에로그가보이는데, 간단히설명하면 serial@com7:telos' 에서읽어오며, platform이 telos이고 client 들은 port 9001번으로접속하라고나와있다. 우측에는서버포트번호와그아래연결된 COM 포트번호가있으며, 바로아래서버를시작 / 중지할수있는버튼이있다. 그아래에 Pckts Read' 카운트숫자가보이며 ( 이것이올라가지않을때는데이터가들어오고있지않다는뜻이다.) Pckts Wrttn 은 COM port로데이터를보내는횟수를표시하고, 그밑두번째에 Num Clients' 는이서버에접속하는 client개수이다. 그밑에도움말버튼과 log 화면을 clear해주는버튼과프로그램종료버튼이위치하고있다. 3.5.2. oscilloscope Java application oscilloscope Java application은 SerialForwarder에서제공해주는 port 로접속해서 data를읽어오고, 이것을 graph로표시해주는 application이다. java 디렉터리로이동 ($>cd /opt/tinyos-1.x/tools/java) application 실행 ($>java net.tinyos.oscope.oscilloscope) - 102 -
실행화면 그림 Ⅱ-3-5 Oscilloscope 실행화면 위그림과같이 6개의채널이있을수있고 sensor의종류에따라나타나지않을수도있다. 만약그래프들이보이지않을경우 Scrolling 박스에체크한다. 그래도보이지않을경우 zoom 버튼을이용해간격을조절하여본다. 3.5.3. SimpleCmd SimpleCmd application은위에서사용했던 TOSBase application을이용하여 RF를통해서각각의 Mote에간단한명령을던져서명령에따라동작하게하는 application이다. 3.5.3.1. Mote application 다운로드 TOSBase를다운로드한다. ($>cd /opt/tinyos-1.x/apps/tosbase) ($>make install <platform>) SimpleCmd application을다운로드한다. ($>cd /opt/tinyos-1.x/apps/simplecmd) ($>make install <platform>) - 103 -
3.5.3.2. PC application의실행명령을내리기위해선두가지의 PC application이필요하다. 하나는위에서보았던 SerialForwarder이고, 다른하나는 PC로부터 TOSBase를통해 RF로명령을보내기위한 BcastInject application 이다. SerialForwarder을실행. ($>java net.tinyos.sf.serialforwarder -comm serial@com7:<platform>&) BcastInject를실행. ($>java net.tinyos.tools.bcastinject <group_id> <command>) <group_id> 는특별한변경이없었을경우 0x7d가 default로설정되어있다. < 참고 > - 그룹 ID 변경하기 ($>cd /opt/tinyos-1.x/tools/make) 디렉터리안에 Makedefaults 라는이름의파일을열어서, 첫번째줄의 DEFAULT_LOCAL_GROUP 상수의값을변경해줌으로써가능하다. <command> 는다음과같은 4가지의명령을내릴수있다. led_on - 노란색 LED를켠다. led_off - 노란색 LED를끈다. radio_louder - radio power를증가시킨다. radio_quieter - radio power를감소시킨다. 3.6. Surge Surge라는 application은노드들간에능동적 (ad-hoc, multi-hop) 으로네트워크를구성하여수집한데이터를 PC로전달하는역할을한다. 또한 TinyOS에서는형성된 network topology를보여주는 Java application을제공하고있다. 3.6.1. Surge 다운로드하기. Surge 디렉터리로이동 ($>cd /opt/tinyos-1.x/apps/surge) application 을 compile 하고 Mote에다운로드 ($>make install.0 <platform>) 위명령에서 install 뒤의 0 이라는숫자는이노드가가지고있는구별되는이름이된다. 숫자를 ID로가짐으로써각각의노드에같은 application이올라가더라도구별되어질수있다. - 104 -
< 참고 > - Mote ID 변경하는또다른방법 </opt/tinyos-1.x/tos/system> 의디렉터리에있는 tos.h 파일을열어서 TOS_LOCAL_ADDRESS으로선언되어있는값을변경시킴으로써 ID의변경이가능하다. ( 단, 0 번 Mote는데이터를 PC로전달해주는 Sink node의역할을해야한다.) 위와같은방법으로몇개의 Mote 를번호를달리해서다운로드한다. 3.6.2. Surge Java application 실행. Sink node가연결된 serial port를 export한다. ($>export MOTECOM=serial@COM1:57600) 위의명령은리눅스에서환경변수를설정하기위한명령인데, MOTECOM이라는이름의환경변수를정의해줌으로써 Surge application에서 Mote가연결된 serial port를알수있다. application을실행한다. ($>cd /opt/tinyos-1.x/tools/java) ($>java net.tinyos.surge.mainclass 0x7d) 0x7d는 groupid의변경이없었을경우 0x7d가 default로설정되어있다. (groupid의변경은이전페이지참조.) 실행화면 그림 Ⅱ-3-6 Surge 실행화면녹색의원이 Mote를나타내고원위의숫자가 MoteID를나타낸다. 몇개의메시지를받았는지가표시되며, 메시지가얼마나정상적으로들어오는지에대한막대그래프가아래표시된다. - 105 -