TinyOS - Informacje wstępne

Rys. 1. Węzeł sieci sensorowej IRIS XM2110.

Rys. 2. Płytka z czujnikami MDA100

Rys. 3. Stacja programująca

Rys. 4. Węzeł w stacji dokującej. Podczas zasilania z portu USB baterie muszą być wyjęte.

1. Rozpoczęcie pracy

1) Obraz dysku maszyny wirtualnej

Pobrać obraz dysku maszyny wirtualnej z dysku sieciowego:

-         Pobierz obraz dysku maszyny wirtualnej Sensor_LAB_v5.vdi (ok. 7 GB, czas pobierania ok. 5 min.) i zapisz na dysku lokalnym, w dowolnym podkatalogu katalogu c:\Designs.

2) Ustawienie klawisza Host

Skorygować w razie potrzeby ustawienie klawisza Host dla Virtual Box: Virtual Box Manager, menu Plik/Globalne ustawienia, nastepnie Wejście i zakładka Maszyna wirtualna: Kombinacja klawiszy gospodarza

Ustawienie prawego Control może powodować konflikty z aplikacjami maszyny wirtualnej, np. z edytorem Emacs.

3) Konfiguracja maszyny wirtualnej

Uwaga: maszyna wirtualna powinna być już zainstalowana na komputerze; jeśli nie jest, to należy pobrać również plik Sensor_LAB_v5.vbox i otworzyć go w Oracle VM Virtual Box poleceniem: Machine/Add.

W ustawieniach maszyny wirtualnej (Settings/Storage/Choose Virtual Hard Disk File...) podaj położenie pliku z obrazem dysku (plik Sensor_LAB_v5.vdi pobrany zgodnie z pkt. 1):

Rys. 5. Ustawienie dysku maszyny wirtualnej

W przypadku problemów z dodaniem dysku (błąd Failed to open hard disk drive... already exists), należy usunąć stare dyski: File/Virtual Media Manager i odłączamy (Release), a następnie usuwamy (Delete) nieistniejące dyski.

Dodatkowo można ustawić folder wspólny, który będzie służył do wymiany informacji pomiędzy maszyną wirtualną z Ubuntu a naszym komputerem. Ustaw folder w Settings/Shared Folders/Edit Shared Folder, aby pasował do naszego katalogu roboczego (zaznaczyć Auto mount, odznaczyć Read Only). Potem wystarczy w Ubuntu przejść do udostępnianego folderu. Aby wyswietlić zamontowan foldery, należy napisac polecenie mount.

Rys. 6. Ustawienie folderu wspólnego

4) Uruchomienie maszyny wirtualnej

Uruchomić maszynę wirtualną Oracle VirtualBox Sensor_LAB z systemem Ubuntu (login: student, w razie potrzeby hasło root'a to: student).

Uruchomić terminal (<lewy Ctrl>+<lewy Alt>+<T>).

5) Sprawdzenie TinyOS

Sprawdzić środowisko TinyOS za pomocą polecnia: tos-check-env

Mogą pojawić się ostrzeżenia, że są nowsze wersje pakietów dodatkowych, np.:

-         java jest w wersji 1.8 a TinyOS wymaga wersji 1.4 lub 1.5. Sprawdzenie aktualnie zainstalowanej wersji java: java –version;

-         graphviz jest w wersji 2.38m a TinyOS wymaga wersji 1.10, ale jest to normalne.

2. Bloki w systemie TinyOS

W katalogu tinyos-main/apps/Sense znajduje się prosty przykład aplikacji uruchamianej na węźle MicaMote odczytującej wartości mierzone przez czujnik. Skopiować folder tinyos-main/apps/Sense do katalogu domowego i na nim wykonywać dalsze działania.

W systemie TinyOS wyróżniamy pliki opisujące konfigurację oraz moduł.

-        Konfiguracja (nazwa pliku ma postać: <nazwa>AppC.nc np. SensorAppC.nc) definiuje połączenia danego modułu z innymi komponentami.

-        Plik z modułem (nazwa pliku w formie: <nazwa>C.nc np.: SensorC.nc) zawiera kod wykonywalny aplikacji, w naszym przypadku aplikacji Sensor.

3. Blok konfiguracji (*AppC.nc)

Każda aplikacja wymaga pliku konfiguracyjnego określającego top-level projektu. W naszym przypadku jest to plik SensorAppC.nc określający konfigurację aplikacji Sensor. Konfiguracja SensorAppC.nc wygląda następująco:

configuration SenseAppC { 
} 
implementation { 
 components SenseC, MainC, LedsC, new TimerMilliC(), new DemoSensorC() as Sensor;
 SenseC.Boot -> MainC;
 SenseC.Leds -> LedsC;
 SenseC.Timer -> TimerMilliC;
 SenseC.Read -> Sensor;
}

Powyższa konfiguracja SensorAppC.nc zawiera osadzony moduł SenseC, który realizuje podstawową funkcjonalność aplikacji oraz inne moduły (MainC, LedsC, TimerMilliC, DemoSensorC) wraz z połączeniami zgodnie z rysunkiem:

Rys. 7. Schemat połączeń w SensorAppC

Niektóre komponenty mogą występować tylko raz w danym systemie – ich wielokrotne osadzenie nie miałoby sensu – są to np. komponenty związane z poszczególnymi urządzeniami sprzętowymi (np. LedsC). Komponenty, które mogą być osadzane wielokrotnie są oznaczane słowem kluczowym new oraz przerywaną linią na powyższym rysunku (np. TimerMilliC). Na powyższym rysunku pojedynczą ramką oznaczono moduły, a podwójną: konfiguracje.

Zmiana nazwy osadzonego komponentu

Podczas osadzania komponentu, można mu nadać inną nazwę. Nazwy zmieniamy, jeśli chcemy, aby były one bardziej czytelne lub gdy osadzamy kilka takich samych modułów (więc ich nazwy muszą być unikalne). W powyższym przykładzie, komponentowi DemoSensorC została zmieniona nazwa na Sensor za pomocą słowa kluczowego as.

new DemoSensorC() as Sensor;

Podobnie można zmieniać nazwy interfejsów.

Interfejsy

Jak opisano powyżej, konfiguracja zawiera opis połączeń pomiędzy modułami. Połączenia są wykonywane pomiędzy interfejsami modułów. Komponent może zapewniać interfejsy (provide), a także ich używać (uses).

Interfejsy provides

Interfejs uses

Te interfejsy są zapewniane przez moduł dla aplikacji, która zawiera osadzony moduł. Dzięki tym interfejsom aplikacja może korzystać z funkcjonalności modułu.

Interfejsy z których moduł korzysta. Są to interfejsy niezbędne komponentowi do realizacji jego funkcji. Służą do komunikacji z innymi modułami osadzonymi w danej konfiguracji.

Można łączyć między sobą tylko takie same (kompatybilne) interfejsy. Interfejsy definiuje się podczas definicji modułu. Jeśli moduł posiada tylko jeden interfejs danego typu, to można pominąć nazwę interfejsu.

SenseC.Boot -> MainC;
SenseC.Leds -> LedsC;
SenseC.Timer -> TimerMilliC;
SenseC.Read -> Sensor;

W powyższym przykładzie pominięto nazwy interfejsów modułów, do których wykonywane są połączenia; formalnie powinno to wyglądać następująco:

SenseC.Boot -> MainC.Boot;
SenseC.Leds -> LedsC.Leds;
SenseC.Timer -> TimerMilliC.Timer;
SenseC.Read -> Sensor.Read;

Moduły osadzone w konfiguracji SenseAppC.nc, tj.: MainC, LedsC, TimerMilliC i Sensor zapewniają (provide) interfejsy, których moduł SenseC używa (uses). Moduł SenseC nie zapewnia żadnych interfejsów, gdyż jest to moduł toplevel w tej aplikacji i nie będzie on wykorzystywany w innych modułach.

Moduł MainC

Moduł MainC jest modułem systemowym i poprzez interfejs Boot sygnalizuje moment zakończenia inicjalizacji węzła po jego uruchomieniu.

4. Blok modułu (*C.nc)

Moduł może wysyłać polecenia (command) do każdego z interfejsów, z których korzysta (uses). Zgodnie z filozofią split-phase programming polecenia są wysyłane do interfejsu bez oczekiwania na odpowiedź zwrotną związaną z realizacją tego polecenia. Dzięki temu nie następuje blokowanie działania programu. Interfejsy mogą zgłosić zdarzenie (event), które jest informacją zwrotną o stanie realizacji danego polecenia. Moduł, który chce wysyłać polecenia do interfejsu, musi zawierać definicje funkcji, które będą wywoływane, gdy interfejsy zgłoszą zdarzenie (event).

Moduł SenseC został opisany w następujący sposób w pliku SenseC.nc:

#include "Timer.h"
module SenseC {
 uses {
 interface Boot;
 interface Leds;
 interface Timer<TMilli>;
 interface Read<uint16_t>;
 }
}
implementation {
 // pominięto część kodu.....
}

Moduł SenseC używa (uses) następujących interfejsów: Boot, Leds, Timer<TMilli> oraz Read<uint16_t>. Niektóre interfejsy mogą zawierać określenie typu danych. W TinyOS typy danych liczbowych są definiowane zgodnie z poniższą tabelą:

 

8 bitów

16 bitów

32 bitów

64 bitów

signed

int8_t

int16_t

int32_t

int64_t

unsigned

uint8_t

uint16_t

uint32_t

uint64_t

Ze względów bezpieczeństwa wszystkie dane są lokalne dla modułu, jakakolwiek wymiana informacji na zewnątrz modułu musi przechodzić przez interfejs. Moduł może zawierać zmienne lokalne, określające jego stan, np.:

uint8_t reading; /* 0 to NREADINGS */

a także funkcje lokalne:

void report_problem() { call Leds.led0Toggle(); }
#include "Timer.h"
module SenseC {
 uses {
 interface Boot;
 interface Leds;
 interface Timer<TMilli>;
 interface Read<uint16_t>;
 }
}

implementation {
 // sampling frequency in binary milliseconds
 #define SAMPLING_FREQUENCY 100
 event void Boot.booted() {
 call Timer.startPeriodic(SAMPLING_FREQUENCY);
 }

 event void Timer.fired() {
   call Read.read();
 }

 event void Read.readDone(error_t result, uint16_t data) {
   if (result == SUCCESS){
     if (data & 0x0004)
       call Leds.led2On()  
     else
       call Leds.led2Off();
   if (data & 0x0002)
     call Leds.led1On();
   else
     call Leds.led1Off();
   if (data & 0x0001)
     call Leds.led0On();
   else
     call Leds.led0Off();
   }
 }
}

Jak widać są tu zdefiniowane funkcje (event) które będą wykonywane w momencie przyjścia zdarzenia z interfejsów. W przypadku przyjścia zdarzenia booted z interfejsu Boot należy za pomocą polecenia startPeriodic skonfigurować i uruchomić Timer, aby generował impuls co określony czas:

 event void Boot.booted() {
   call Timer.startPeriodic(SAMPLING_FREQUENCY);
 }

Gdy przyjdzie zdarzenie z Timera, należy zlecić odczyt z interfejsu Read:

 event void Timer.fired() {
   call Read.read();
 }

Gdy odczyt będzie gotowy, należy wyświetlić go na diodach LED. Ponieważ są dostępne tylko 3 diody, więc wyświetlamy 3 najmniej znaczące bity:

 event void Read.readDone(error_t result, uint16_t data) {
   if (result == SUCCESS){
     if (data & 0x0004)
       call Leds.led2On();
     else
       call Leds.led2Off();
     if (data & 0x0002)
       call Leds.led1On();
     else
       call Leds.led1Off();
     if (data & 0x0001)
       call Leds.led0On();
     else
       call Leds.led0Off();
   }
 }
}

5. Kompilacja

Aby skompilować aplikację, należy wykonać polecenie (w katalogu aplikacji, np. Sense):

make iris

W przypadku sukcesu, powinniśmy otrzymać następujące komunikaty:

mkdir -p build/iris
 compiling SenseAppC to a iris binary
ncc -o build/iris/main.exe -Os -fnesc-separator=__ -Wall -Wshadow -Wnesc-all -target=iris -fnesc-cfile=build/iris/app.c -board=micasb -DDEFINED_TOS_AM_GROUP=0x22 --param max-inline-insns-single=100000 -DIDENT_APPNAME=\"SenseAppC\" -DIDENT_USERNAME=\"student\" -DIDENT_HOSTNAME=\"student-Virtual\" -DIDENT_USERHASH=0xf0999aa1L -DIDENT_TIMESTAMP=0x5501abd7L -DIDENT_UIDHASH=0x692fd64bL -fnesc-dump=wiring -fnesc-dump='interfaces(!abstract())' -fnesc-dump='referenced(interfacedefs, components)' -fnesc-dumpfile=build/iris/wiring-check.xml SenseAppC.nc -lm 
 compiled SenseAppC to build/iris/main.exe
 3126 bytes in ROM
 47 bytes in RAM
avr-objcopy --output-target=srec build/iris/main.exe build/iris/main.srec
avr-objcopy --output-target=ihex build/iris/main.exe build/iris/main.ihex
 writing TOS image

6. Zaprogramowanie węzła

Jeśli aplikacja kompiluje się poprawnie, można zaprogramować węzeł. W celu zaprogramowania węzła, należy wyjąć baterie z węzła i zainstalować go w stacji bazowej wraz z płytką czujników.

Rys. 8. Fotografia węzła wraz z płytką czujników zainstalowanych w stacji bazowej

UWAGA: Instalacja węzła z bateriami z jednoczesnym podłączeniem do portu USB spowoduje uszkodzenie węzła!

1)  Podłączyć zmontowany zestaw do komputera poprzez port USB.

2)  Uaktywnić port USB Crossbow MIB520CA w maszynie wirtualnej (kliknąć prawym przyciskiem myszy w prawym dolnym rogu VirtualBox na ikonie ).

Rys. 9.

       3)  Sprawdzić numery portów USB za pomocą polecenia:

dmesg | grep ttyUSB

dmesg = wyświetlenie komunikatów startowych ładowania systemu

oraz

ls /sys/class/tty 

Stacja dokująca USB udostępnia dwa porty o kolejnych numerach. Niższy numer służy do programowania, a wyższy do komunikacji z węzłem. Typowo są to porty:

/dev/ttyUSB0 - do programowania;

/dev/ttyUSB1 - do komunikacji.

W przypadku problemów z podłączeniem portów USB, należy zastosować następującą procedurę:

  • przed rozpoczęciem pracy trzeba zrestartować komputer: w Command Prompt: shutdown  /r /t 0
  • poczekać, aż Ubuntu się w pełni uruchomi (na wszelki wypadek)
  • po kliknięciu na dole i włączeniu portów USB (Crossbow) przez chwilę nic się nie dzieje - trzeba chwilę poczekać (nie podłączać drugi raz!). Po ok. 20 sekundach powinno być tak (na poniższym przykładzie są podłączone dwa sensory, więc są 4 porty USB):

4)  Za pomocą polecenia

make iris install,<nr_węzła> mib520,/dev/ttyUSB<nr_portu_USB>

np.:

make iris install,0 mib520,/dev/ttyUSB0

zaprogramować węzeł. Jako numer węzła <nr_węzła>, podać numer swojego komputera (1-15) w celu uniknięcia konfliktów z innymi studentami. W miejsce <nr_portu_USB> podajemy numer niższego portu USB, zazwyczaj 0. Przykładowe komunikaty po poprawnym zaprogramowaniu:

avrdude: 14384 bytes of flash written
avrdude: verifying flash memory against build/iris/main.srec.out-101:
avrdude: load data flash data from input file build/iris/main.srec.out-101:
avrdude: input file build/iris/main.srec.out-101 auto detected as Motorola S-Record
avrdude: input file build/iris/main.srec.out-101 contains 14384 bytes
avrdude: reading on-chip flash data:
 
Reading | ################################################## | 100% 2.42s
 
avrdude: verifying ...
avrdude: 14384 bytes of flash verified
 
avrdude: safemode: Fuses OK
 
avrdude done. Thank you.
 
rm -f build/iris/main.exe.out-101 build/iris/main.srec.out-101

7. Dokumentacja projektu

Za pomocą polecenia:

make iris docs 

można wygenerować dokumentację naszego projektu.

W przeglądarce systemu Ubuntu (np. Firefox) należy wpisać adres pliku index.html, jako:

file:///home/student/Sense/doc/nesdoc/iris/index.html

Obejrzeć dokumentację implementacji: po lewej stronie przewinąć do dołu, aby pojawiły się komponenty (Components) i wybrać komponent SenseAppC.

Rys. 10. Dokumentacja projektu

8. Dodanie możliwości drukowania komunikatów za pomocą printf:

Komponent, który jest podłączony przez stację bazową do komputera, może wysyłać komunikaty tekstowe przez port szeregowy. Takie komunikaty są bardzo pomocne w uruchamianiu oprogramowania. Poniższa procedura umożliwi drukowanie dowolnych komunikatów za pomocą standardowej instrukcji printf:

1)  W głównym pliku Makefile projektu dodać ścieżkę do modułu printf:

CFLAGS += -I$(TOSDIR)/lib/printf

2)  Do pliku konfiguracji *AppC.nc dodać polecenia:

#define NEW_PRINTF_SEMANTICS
#include "printf.h"
...

oraz dołączyć następujące dwa komponenty:

components PrintfC;
components SerialStartC;

3)  W pliku opisującym działanie modułu (*C.nc) dodać nagłówek:

#include "printf.h"

Od tego momentu można używać (np. w event'ach) polecenia printf:

printf("Hi I am writing to you from my TinyOS application!!\n");
printf("Here is a uint8: %u\n", dummyVar1);
printf("Here is a uint16: %u\n", dummyVar2);
printf("Here is a uint32: %ld\n", dummyVar3);
printfflush();

Na koniec wydruków dobrze jest umieścić polecenie printfflush(), aby przyspieszyć pojawienie się komunikatu.

W celu przechwycenia danych wysyłanych na port przez węzeł, należy uruchomić aplikację w terminalu komputera PC przechwytującą komunikaty z portu szeregowego:

java net.tinyos.tools.PrintfClient -comm serial@/dev/ttyUSB<nr>:iris

typowo:

java net.tinyos.tools.PrintfClient -comm serial@/dev/ttyUSB1:iris

 

Należy pamiętać, aby podać numer portu USB <nr> przeznaczonego do komunikacji (wyższy numer).

Dodatkowe informacje

Dokumentacja

http://tinyos.stanford.edu/tinyos-wiki/index.php/TinyOS_Documentation_Wiki

Zmiana rozmiaru okna maszyny wirtualnej

W razie problemów ze skalowaniem okna obrazu w VirtualBox, zainstalować pakiet w systemie Ubuntu:

sudo apt-get install virtualbox-guest-dkms

Wspólny folder

W celu wymiany informacji pomiędzy komputerem PC a maszyną wirtualną, w Oracle Virtual Box należy zdefiniować Shared folder (Oracle Virtual Box / Devices/Shared Folder Settings...).

Następnie w Ubuntu należy podłączyć udostępniany folder, np. poleceniem:

sudo mount -t vboxsf -o uid=$UID,gid=$(id -g) lab_zss /mnt

Od tego momentu udostępniany folder będzie dostępny w Ubuntu jako /mnt.

Opracował: dr hab. inż. M. Wójcikowski