Introducción
Para cumplir a los objetivos establecidos en este proyecto, primero es necesario ambientarse en el lenguaje de NesC.
Por lo tanto en esta presentación usaremos un ejemplo de transmisión RFM para indicar la estructura de comunicación de los motes. Luego se mostrarán las herramientas de simulación y depuración que contiene TinyOS, indicando sus principales características. Finalmente daremos una introducción a la adquisición de datos de un GPS.
NesC: Comunicación RFM
Grafo de Componentes: Transmision de Datos
Grafo de Componentes: Recepción de Datos
CntToRfm
Esta aplicación consta solo de la parte de configuración, todos sus módulos ya están hechos y se ubican en el directorio tos/lib/Counters.
A continuación veamos el archivo CntToRfm.nc:
|
configuration CntToRfm {
}
implementation {
components Main, Counter, IntToRfm, TimerC;
Main.StdControl -> Counter.StdControl;
Main.StdControl -> IntToRfm.StdControl;
Counter.Timer -> TimerC.Timer[unique("Timer")];
Counter.IntOutput -> IntToRfm.IntOutput;
} |
Como se puede apreciar una singular interfaz puede tener múltiples implementaciones (o conexiones). En este caso la interfaz Main.StdControl es enlazada a las componentes Counter e IntToRfm. Counter recibe el evento Timer.fired() para mantener un contador. IntToRfm provee la interfaz IntOutput, la cual tiene como comando a output(), que es llamado con un valor de 16-bit, y un evento outputComplete(), que es llamado con un valor de tipo result_t (SUCCESS o FAIL). IntToRfm envía los paquetes mediante broadcast sobre su radio.
Counter: Generación de Datos
A continuación se muestra la implementación del Módulo Counter, el cual básicamente construye un contador (variable state) mediante el evento fired().
|
implementation {
int state;
command result_t StdControl.init()
{
state = 0;
return SUCCESS;
}
command result_t StdControl.start()
{
return call Timer.start(TIMER_REPEAT, 250);
}
command result_t StdControl.stop()
{
return call Timer.stop();
}
event result_t Timer.fired()
{
state++;
return call IntOutput.output(state);
}
event result_t IntOutput.outputComplete(result_t success)
{
if(success == 0) state --;
return SUCCESS;
}
} |
El evento outputComplete() se activa desde la componente IntToRfmM, através de la sentencia "signal", la cual envía un valor de tipo result_t (SUCCESS o FAIL). Esto permite manejar el caso en que el mensaje no pudo ser enviado, y requiere ser retransmitido (state--).
IntToRfm: Envío de Mensajes
Esta componente recibe un valor (mediante la interfaz IntOutput) y transmitirlo via broadcast sobre el radio.
El archivo IntToRfm.nc contiene lo siguiente:
|
includes IntMsg;
configuration IntToRfm
{
provides {
interface IntOutput;
interface StdControl;
}
}
implementation
{
components IntToRfmM, GenericComm as Comm;
IntOutput = IntToRfmM;
StdControl = IntToRfmM;
IntToRfmM.Send -> Comm.SendMsg[AM_INTMSG];
IntToRfmM.SubControl -> Comm;
} |
Esta componente provee de las interfaces IntOutput y StdControl, permitiendo que otras componentes puedan crear conexiones.
En la seccion de implementacion se puede ver:
|
components IntToRfmM, GenericComm as Comm;
IntOutput = IntToRfmM;
StdControl = IntToRfmM;
IntToRfmM.Send -> Comm.SendMsg[AM_INTMSG];
IntToRfmM.SubControl -> Comm; |
En la primera línea se puede ver la frase GenericComm as Comm solo permite definirle un nombre local a la componente GenericComm
. Luego las dos siguientes líneas son usadas para indicar que la interfaz IntOutput proveída por IntToRfm es "equivalente a" la implementación en IntToRfmM (implementación del módulo). Acá no se está conectando una interfaz a una implementación dada. En este caso se está igualando las interfaces dadas por IntToRfm con la implementación hecha en IntToRfmM.
Las ultimas dos
líneas de la configuración permiten hacer la conexión de IntToRfmM.StdControl a GenericComm.StdControl y de IntToRfmM.Send a la interfaz parametrizada GenericComm.SendMsg[AM_INTMSG]. La interfaz SendMsg dada por Comm tiene el siguiente prototipo:
|
interface SendMsg[uint8_t id]; |
En otras palabras, esta componente provee 256 instancias diferentes para esta interfaz. Este es el camino para conectar esta interfaz con el ID de los mensajes que seran transmitidos por la red, y así permitir la comunicación con otros nodos. En IntToRfm, se está conectando la interfaz SendMsg con el ID AM_INTMSG a GenericComm.SendMsg (AM_INTMSG es una variable global defineda en tos/lib/Counters/IntMsg.h).
Cuando el comando SendMsg es invocado, el ID es enviado como un argumento extra. Se puede apreciar en el archivo de implementacion del modulo para GenericComm (tos/system/AMStandard.nc).
|
command result_t SendMsg.send[uint8_t id]( ... ) { ... }; |
IntToRfmM: Módulo de Comunicación
Este punto veremos como se implementa la comunicación de mensajes. A continuación veremos el código del comando IntToRfmM.output() en IntToRfmM.nc
|
bool pending;
struct TOS_Msg data;
/* ... */
command result_t IntOutput.output(uint16_t value) {
IntMsg *message = (IntMsg *)data.data;
if (!pending) {
pending = TRUE;
message->val = value;
atomic {
message->src = TOS_LOCAL_ADDRESS;
}
if (call Send.send(TOS_BCAST_ADDR, sizeof(IntMsg), &data))
return SUCCESS;
pending = FALSE;
}
return FAIL;
} |
Este comando usa una estructura de mensaje llamada IntMsg, declarada en tos/lib/Counters/IntMsg.h, teniendo el siguiente aspecto:
|
command result_t SendMsg.send[uint8_t id]( ... ) { ... }; |
Es esta simple estructura con los campos val and src. El primero es el dato en sí, en cambio src representa la direccion fuente del mensaje. Cuando llamamos al comando send(), le pasamos como argumento la dirección destino, el tamaño de datos a enviar y los datos propiamente tal.
TOS_msg es la estructura base para los datos y que usa el comando SendMsg.send(). Es declarada en tos/system/AM y contiene campos para la dirección destino, el tipo de mensaje (ID), el largo, los datos, etc. El tamaño maximo de los datos está definido mediante la variable global TOSH_DATA_LENGTH y por defecto está configurada a 29. En este caso se está encapsulando un dato de tipo IntMsg en el campo datos de la estructura TOS_Msg.
El comando SendMsg.send() envía una señal generando el evento SendMsg.sendDone() cuando la transmision del mensaje es completada. Si send() finaliza correctamente, el mensaje queda en cola de transmisión, de lo contrario genera un mensaje de fallo (este ejemplo lo utiliza para retransmitir el mensaje).
RfmToLeds
La aplicación RfmToLeds se define como una simple configuración que usa la componente RfmToInt para recibir mensajes y la componente IntToLeds para mostrar los datos recibidos en los LEDs. Así como IntToRfm, la coponente RfmToInt usa a GenericComm para recibir mensajes.
|
includes IntMsg;
configuration RfmToInt {
provides interface StdControl;
uses interface IntOutput;
}
implementation {
components RfmToIntM, GenericComm;
IntOutput = RfmToIntM;
StdControl = RfmToIntM;
RfmToIntM.ReceiveIntMsg -> GenericComm.ReceiveMsg[AM_INTMSG];
RfmToIntM.CommControl -> GenericComm;
} |
En esta línea se especfica que mensaje con ID AM_INTMSG debería ser conectado a la interfaz RfmToIntM.ReceiveMsg. La interfaz ReceiveMsg (tos/interfaces/ReceiveMsg.nc) solo declara un evento: receive(), que es señalizado con un puntero al mensaje recibido.
|
implementation {
command result_t StdControl.init() {
return call CommControl.init();
}
command result_t StdControl.start() {
return call CommControl.start();
}
command result_t StdControl.stop() {
return call CommControl.stop();
}
event TOS_MsgPtr ReceiveIntMsg.receive(TOS_MsgPtr m) {
IntMsg *message = (IntMsg *)m->data;
call IntOutput.output(message->val);
return m;
}
event result_t IntOutput.outputComplete(result_t success) {
return SUCCESS;
} } |
Nota que en el comando receive() se retorna el buffer original del mensaje. Si la componente necesita almacenar el contenido para mas tarde, se necesita copiar el mensaje a un nuevo buffer, o retornar un nuevo buffer de mensaje para ser usado por el stack de red.
Herramientas Útiles de TinyOS
TOSSIM
TOSSIM o TinyOS Simulator, se compila directamente sobre el código de TinyOS. La simulación corre nativamente sobre el sistema operativo y su compilación se hace con make pc.
Dentro de sus características principales está la capacidad de depurar seleccionando las características que se desean ver sin necesidad de recompilar (datos mandados, variables internas, reloj, etc)
Debug en TinyOS
Para demostrar la capacidad de depurar código, se usará como ejemplo el programa apps/CntToLedsAndRfm. En este caso queremos que el simulador nos muestre los valores de los LED y el AM (Active Message).
Primero compilamos el programa
$ make pc
Con esto se crea el directorio build/pc/ que contiene el archivo main.exe, entonces lo ejecutamos para que genere una simulación con 10 motes:
|
3: RFM: Mote 3 got bit 0
3: Popping event for mote 3 with time 0:0:0.56359975.
3: Setting TOS_LOCAL_ADDRESS to 3
3: RADIO: Channel Mon event handled for mote 3 at 0:0:0.56359975 with interval o
f 200.
|
El resultado muestra valores que no nos interesan, entonces para seleccionar lo que queremos usar usamos main –help, el cual nos muestra las siguientes opciones de debugeo:
|
$ build/pc/main.exe --help
Usage: build/pc/main [options] num_nodes
[options] are:
-h, --help Display this message.
-gui pauses simulation waiting for GUI to connect
-a=<model> specifies ADC model (generic is default)
options: generic random
-b=<sec> motes boot over first <sec> seconds (default 10)
-e=<file> use <file> for eeprom; otherwise anonymous file is used
-l=<scale> run sim at <scale> times real time (fp constant)
-r=<model> specifies a radio model (simple is default)
options: simple lossy
-rf=<file> specifies file for lossy mode (lossy.nss is default)
implicitly selects lossy model
-s=<num> only boot <num> of nodes
-t=<sec> run simulation for <sec> virtual seconds
num_nodes number of nodes to simulate
|
Known dbg modes: all, boot, clock, task, sched, sensor, led, crypto, route, am,
crc, packet, encode, radio, logger, adc, i2c, uart, prog, sounder, time, sim, qu
eue, simradio, hardware, simmem, usr1, usr2, usr3, temp, error, none
Para activar un modo de depuración de los LED y AM usamos
Y luego volviendo a ejecutar (esta vez sólo con dos motes)
|
$ build/pc/main.exe
1: AM_address = ffff, 4; counter:1
1: Received message:
ff ff 04 7d 08 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 c3 f4 00 00
1: AM_type = 4
1: LEDS: Red on.
1: LEDS: Green off.
1: LEDS: Yellow off.
1: Sending message: ffff, 4
ff ff 04 7d 08 09 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 60 6f 00 00 01 00 c9 8e 00 00
0: AM_address = ffff, 4; counter:4
0: Received message:
ff ff 04 7d 08 09 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 0e 80 00 00
0: AM_type = 4
0: LEDS: Red off.
0: LEDS: Green off.
0: LEDS: Yellow on.
0: Sending message: ffff, 4
ff ff 04 7d 08 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 cf f2 00 00 01 00 7c d1 00 00
1: AM_address = ffff, 4; counter:2
1: Received message:
ff ff 04 7d 08 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 b1 b4 00 00
1: AM_type = 4
1: LEDS: Red off.
1: LEDS: Green on.
1: LEDS: Yellow off.
1: Sending message: ffff, 4
Exiting on SIGINT at 0:0:9.92429775.
|
Como agregar una declaración de depuración
Así como existen valores para depurar que ya están implícitos dentro de TOSSIM, existe la opción de monitorear variables de un código, esto se logra asignando una de las variables usr1, usr2, usr3 o temp, con el comando:
dbg(<mode>, const char* format, ...);
Por ejemplo:
1) Abrir tos/lib/Counters/Counter.nc
2) En la línea Timer.fired(), agregar antes de return
3) dbg(DBG_TEMP, "Counter: Value is %i\n", (int)state);
4) en la línea de comandos $ export DBG=temp
5) ejecutar el código
Depuración Gráfica
TinyViz es una herramienta de depuración gráfica que puede interactuar con TOSSIM. Con ella se pueden ver tiempos de ejecución, agregar breakpoints, además de agregar plug-ins para la mejor visualización de los datos.
Primero es importante compilar TinyViz, para eso ejecutamos make en la carpeta /opt/tinios-1.x/tools/java/net/tinyos/sim y agregamos esta ruta al PATH
|
$ export PATH=$PATH:/opt/tinyos-1.x/tools/java/net/tinyos/sim |
Con la preparación previa lista, ahora sólo es necesario compilar un programa (en este caso se usará de ejemplo el programa apps/CntToLedsAndRfm)
El comando a utilizar es
|
$ tinyviz –run build/pc/main.exe 5 |
Haciendo esto tinyviz muestra nos muestra una ventana
La práctica demuestra que generar una simulación crea mucha carga sobre las estaciones de trabajo, por lo que la velocidad de ejecución es demasiado lenta. Es por eso que se recomienda primero fijar los datos que se deseen ver en el debug.
Y luego ejecutar el programa de la siguiente manera
|
$build/pc/main.exe –gui 5 &
$ tinyviz |
Luego de una espera, tinyviz se conecta con el programa y mejora su rendimiento y velocidad de simulación.
GPS
Adquisición de Datos en un GPS
Para poder buscar una funcionalidad y la unión del proyecto con la solución actual de TMV, se busca conseguir datos del GPS y guardarlos dentro de un logger (solución actual) y extender con la red de sensores inalámbricos su utilidad.
Para esto es necesario obtener los datos del GPS, y para conseguir esta meta, es necesario usar el protocolo NMEA 0180 (National Marine Electronics Association).
NMEA 0180 (desde ahora NMEA) es un protocolo que estandariza la conexión eléctrica y el protocolo de datos de los dispositivos de la marina, en nuestro caso, del GPS.
El protocolo pide de salida de datos serial RS-232 con 4800 baudios, 8 bits de datos, sin paridad y 1 bit de parada.
Los mensajes deben empezar con signo peso ($), no se deben usar espacios entre los datos, si no comas (,) y terminar con un retorno de carro y linefeed <CR><LF>
Para más detalle ver "Ejemplo de Comunicación y Estructura de Datos Seriales". |