Argumentos de entrada en simulaciones de NS3

Para ahorrarte trabajo, puedes configurar mediante argumentos de entrada muchos parámetros o atributos de tu simulación, tanto de los módulos que usas de NS3, como definir tus propios parámetros. Por ejemplo, si vas a simular el tráfico en una red lorawan con topología en estrella, con la pasarela en el medio, puedes poner como parámetro configurable el número de nodos o el radio de la estrella. Este ejemplo es el que vamos a utilizar para explicar cómo gestionar los argumentos de entrada. El archivo que usaremos es el lorawan.cc del repositorio. Necesitarás instalar el módulo de lorawan como indicamos aquí

Si copiamos el archivo lorawan.cc al directorio scratch de nuestra instalación NS3 podemos ver qué argumentos podemos definir por la linea de comandos mediante la opción –PrintHelp, esto es:

$ ./waf --run "lorawan --PrintHelp"
lorawan [Program Options] [General Arguments]
[...]
Program Options:
--radio: Radio of the disc where random put the nodes [20]
--numnodes: Num. nodes in the grid for simulating [20]


General Arguments:
--PrintGlobals: Print the list of globals.
--PrintGroups: Print the list of groups.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintTypeIds: Print all TypeIds.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintHelp: Print this help message.

Como podemos ver, después de los mensajes de compilación (sustituidos por […]), podemos ver que el programa lorawan tiene definidos dos opciones, el radio y el numnodes, indicando el radio de la estrella y el número de nodos respectivamente. El número por defecto en ambos casos y si no indicamos esas opciones es 20. Si queremos simular 3 nodos en una topología en estrella con un radio de 10 metros, solo tendremos que indicarlo en línea de argumentos:
$ ./waf --run "lorawan --numnodes=3 --radio=10"
[...]

Si analizamos el archivo lorawan.cc, la definición de argumentos es relativamente sencilla, en primer lugar se indica que vamos a parsear la línea de comandos en busca de atributos y se definen las variables que almacenarán los valores que se pasen por línea de comandos:

#include "ns3/command-line.h"
[...]
double radio = 20.0;
int numnodes = 20;

A continuación se definen los valores esperados en línea de argumentos, en nuestro ejemplo, lo hacemos mediante una función que definimos antes del main y añadimos al final. En esta función creamos la variable que almacenará y parseará los argumentos y añadimos nuestros dos argumentos esperados mediante AddValue:

CommandLine setupcmd(){
CommandLine cmd;
cmd.AddValue ("radio", "Radio of the disc where random put the nodes", radio);
cmd.AddValue ("numnodes", "Num. nodes in the grid for simulating",numnodes);
return cmd;
}

como puedes ver indicamos el nombre, una pequeña ayuda y las variables que van a almacenar los valores.
Finalmente, parseamos la línea de comandos:

[..]
CommandLine cmd =setupcmd();
cmd.Parse (argc, argv);
[..]

Con lo que en las variables definidas a tal efecto, guardaremos los valores que le pasemos.

Aparte de poder definir nuestros propios argumentos de entrada para configurar nuestras simulaciones, muchos módulos que usamos tienen sus propios atributos configurables por líneas de comandos. Por ejemplo, si queremos ver un listado de todos los módulos disponibles, podemos verlos con la opción –PrintTypeIds

./waf --run "lorawan --PrintTypeIds"
Waf: Entering directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.112s)
Registered TypeIds:
ns3::A2A4RsrqHandoverAlgorithm
ns3::A3RsrpHandoverAlgorithm
[..]
ns3::LoraChannel
ns3::LoraInterferenceHelper
ns3::LoraNetDevice
ns3::LoraPhy
ns3::LoraRadioEnergyModel
ns3::LoraTag
ns3::LoraTxCurrentModel
[...]

En esa larga lista, podemos explorar qué atributos son configurables mediante la opción –PrintAttributes y el identificador o nombre del módulo. Por ejemplo, si queremos saber qué atributos puedo especificar mediante línea de comandos del módulo de ns3 LoraRadioEnergyModel, podemos ejecutar:

$ ./waf --run "lorawan --PrintAttributes=ns3::LoraRadioEnergyModel"
Waf: Entering directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.063s)
Attributes for TypeId ns3::LoraRadioEnergyModel
--ns3::LoraRadioEnergyModel::RxCurrentA=[0.0112]
The radio Rx current in Ampere.
--ns3::LoraRadioEnergyModel::SleepCurrentA=[1.5e-06]
The radio Sleep current in Ampere.
--ns3::LoraRadioEnergyModel::StandbyCurrentA=[0.0014]
The default radio Standby current in Ampere.
--ns3::LoraRadioEnergyModel::TxCurrentA=[0.028]
The radio Tx current in Ampere.
--ns3::LoraRadioEnergyModel::TxCurrentModel=[0]
A pointer to the attached tx current model.



Donde podemos ver que podemos definir la corriente de envío, recepción, dormir, etc.

Permisos de archivos en GNU/Linux

Los permisos de acceso a los archivos en GNU/Linux limitan quién puede acceder a un archivo y qué puede hacer con ese archivo. Se define el perfil de propietario del archivo (el usuario que lo creó), el grupo al cual pertenece dicho archivo (que por defecto es al grupo del usuario que lo creó), y el resto de usuarios que no son ni el propietario, ni pertenecen al grupo al cual pertenece el archivo. Para cada uno de esos perfiles se define si el usuario puede acceder para leer el archivo, escribir en él o ejecutarlo.
¿Cómo saber qué permisos tiene un archivo?, bien, la forma más fácil es con el comando ls, indicando que te dé todos archivos, incluso los que empiezan por «.» con la opción -a. En la figura podemos ver un ejemplo con la opción l para que liste los archivos, y la h, si quieres que el tamaño salga en múltiplos cómodos de leer (si no, sale un número en bytes). En la figura podemos ver cómo interpretar toda la salida del comando ls -alh en un directorio con un único archivo (hola.txt).

Atendiendo a la información que muestra el comando leída de izquierda a derecha, podemos ver que el archivo hola.txt es un archivo, el propietario tiene permisos de lectura y escritura, el grupo y el resto de usuarios solo pueden leerlo, tiene un único enlace a dicho archivo (el 1 sin leyenda), lo creó el usuario alumno, pertenece al grupo de alumno, tiene 5 bytes, se creó el 18 de enero a las 11:56 y se llama hola.txt.
¿Cómo puedo cambiar los permisos de un archivo?, con el comando chmod, por ejemplo, si pudiéramos ejecutar el archivo hola.txt y quisiéramos darle permisos de ejecución, bastaría con chmod +x hola.txt

Comandos, opciones y argumentos en la consola GNU/Linux

Vamos a repasar los conceptos de comando, opciones y argumentos mientras continuamos gestionando archivos. Si tienes claro estos conceptos avanzarás más rápido en tu dominio de la consola.
¿Qué es un comando? es una orden que se ejecuta en el terminal y que ejecuta un programa con una funcionalidad determinada. Por ejemplo, la orden ls ejecuta el programa ls que lista por pantalla los archivos de un directorio.
¿Qué es una opción? es una indicación, que se pone a continuación del comando, para el programa que se ejecuta para que haga o muestre algo diferente. Generalmente, en su forma corta es una letra precedida por un guion, por ejemplo -l. En el comando ls, la opción -l lista los archivos en forma de lista con sus permisos, usuario, grupo, tamaño, fecha de creación y nombre. De esta forma, si queremos visualizar los archivos de esta forma, debemos añadir la opción -l a la orden ls: ls -l. Algunas opciones pueden tener un formato largo que es una palabra, precedida por dos guiones, ej. – -help. La opción – -help en la mayoría de los comandos imprime una ayuda básica con las opciones disponibles.
¿Qué es un argumento? es de nuevo una indicación, que generalmente se pone a continuación del comando y las opciones, sobre qué directorio o archivo hay que ejecutar el programa indicado. Por ejemplo, si queremos listar los archivos de un directorio concreto, lo tenemos que indicar al final de la orden. ls -l /home/usuario/directorio listará en forma de lista, los archivos del directorio /home/usuario/directorio.

Si no indicas ninguna opción y/o argumento, el programa tiene habilitadas unas opciones por defecto que no necesitan indicarse. El comando ls por defecto lista los archivos del directorio donde se ejecuta.
La estructura de archivos y directorios tiene forma de árbol donde el «tronco» sería el elemento raiz o directorio inicial / y luego está formado por subdirectorios del sistema operativo y, dentro del directorio «home», cada usuario tiene un directorio para sus archivos personales.
En la figura de arriba fíjate que hay dos formas equivalentes de indicar que liste los subdirectorios y archivos del directorio uclm.
El comando man es el comando de ayuda del terminal y si le proporcionas como argumento el comando que sea, te mostrará la ayuda de ese comando (si tiene ayuda).

Comandos básicos de la consola, gestión de archivos (Debian 10: Bash)

Entre los comandos más habituales que ejecutamos en la consola nos encontramos todo lo que tiene que ver con la gestión de archivos y directorios. El directorio inicial o raiz viene representada por la barra / y es equivalente al directorio C:\ de windows. A partir de esa ruta inicial se crea una estructura de directorios y archivos con sus características asociadas.

Con el comando ls vemos los archivos y directorios del directorio que le indiquemos. Si no ponemos nada, por defecto, lista los directorios y archivos del directorio donde se ejecuta.

Se utiliza la barra / para separar cada directorio, de esta forma, /home/alumno/ejemplo/ es un directorio home que se encuentra en el directorio raíz y dentro del cual hay un directorio llamado alumno que a su vez alberga un directorio ejemplo. En bash, el punto «.» representa el directorio actual donde se encuentra la consola, los dos puntos «..» representa el directorio anterior en la ruta al cual está la consola, y el símbolo de la ñ «~» representa el directorio de usuario actual, por ejemplo, si el usuario es alumno, la ~ representaría la ruta /home/alumnno
Con el comando «cd DIRECTORIO» nos vamos al directorio que le indiquemos a continuación como argumento.
De esta forma, si nos encontramos en el directorio /home/alumno, y queremos ir al directorio ejemplo que se encuentra en dicho directorio, podemos especificar la ruta de varias formas:

  1. La ruta relativa al directorio donde se ejecuta el comando: cd ejemplo
  2. La ruta absoluta: cd /home/alumno/ejemplo
  3. La ruta relativa al directorio actual indicado explícitamente: cd ./ejemplo
  4. La ruta relativa al directorio padre del actual: cd ../alumno/ejemplo

Con el comando «mkdir DIRECTORIO» creamos un directorio o directorios si no existen. Por defecto lo creamos en el directorio actual pero se pueden especificar rutas completas. Creemos el directorio ejemplo2, pondremos varias formas para afianzar el concepto de ruta:

  1. mkdir ejemplo2
  2. mkdir /home/alumno/ejemplo2
  3. mkdir ~/ejemplo2

En la figura de abajo podemos ver un ejemplo creando un directorio uclm con la facultad esi dentro de ella y el edificio fermin-caballero, a su vez, dentro de la esi. Finalmente, con el comando cd, nos vamos al último directorio creado.

Comandos de la consola GNU/Linux (Debian 10: Bash)

Un comando es una orden que interpreta el intérprete de comandos y que sirve para interaccionar con el sistema operativo. Hay órdenes para realizar todo, gestionar archivos, ejecutar aplicaciones, administrar servicios de red, explorar internet, diagnosticar tu computador, etc.
Un comando está formado por el nombre del comando, una o varias opciones que modifican el comportamiento del comando y uno o varios argumentos que indican la ruta o archivo con el que el comando va a trabajar. Las opciones tienen un formato corto, una letra precedida por un guión y un formato largo, una palabra que indica la opción precedida por dos guiones. Aunque depende del programador que realizó el comando, algo más o menos estándar es que la opción -h imprima la ayuda del comando. Esa misma opción tiene un formato largo –help. Como ya he comentado, aunque existen prácticas comunes en cuanto a las opciones asociadas a la palabra en inglés que identifica la opción (h para la ayuda, v para sacar información de los pasos que está haciendo, etc.), cada comando puede seguir sus propias reglas.

Antes de empezar a ver ejemplos tres cosas que debes tener en cuenta:

  1. Los intérpretes de comandos en linux son sensibles a las mayúsculas por lo tanto ls es un comando distinto a LS. Por lo general, los nombres de comandos todos en minúsculas. Lo mismo para las opciones, generalmente, no es lo mismo -p que -P, aunque esto depende del creador del comando y, aunque recomendable, no está tan estandarizado.
  2. El tabulador autocompleta nombres de comandos, de directorios y de archivos. Aquí reside gran parte de la potencia de la consola y de su alta productividad, acostúmbrate a utilizar el tabulador. Generalmente la configuración por defecto está habilitada, si no, hay que habilitarla. Si quieres usar el comando mkdir, teclea mk y pulsa tabulador, si hay más de un comando que empieza por mk no hará nada, pulsa otra vez el tabulador y te sugerirá todos los comandos que empiezan por mk, sigue tecleando y cuando no sea ambiguo, si pulsas tabulador te lo completará.
  3. No hace falta «estudiarte» los comandos, poco a poco, los que más uses se te irán quedando, es buena idea imprimirte una hoja de comandos (por ejemplo, esta, esta otra, o esta) al principio para ir mirando los más habituales. En breve no te hará falta

Vamos a ver un ejemplo antes de seguir. El comando ls lista los directorios y archivos que hay dentro de un determinado directorio. En el gif de abajo, la primera vez que ejecutamos el comando ls, no ponemos ninguna opción ni argumento. Esto hace que ls coja las opciones y argumentos habilitadas por defecto, con lo cual, nos saca un listado de nombres con un código de colores (los azules son directorios) y del directorio donde se ejecuta ls.

Si queremos mas información podemos usar la opción -l, que te lista los archivos y directorios con mucha más información (que veremos en una futura entrada) del directorio en el cual ejecutamos el comando.
Si queremos listar los archivos y directorios, debemos indicárselo al comando ls, eso es lo que hacemos en las dos últimas ejecuciones, indicándole que liste el directorio padre de donde estoy actualmente (se indica con los dos puntos ..) por lo que lista el directorio del usuario alumno, y que liste el directorio ejemplo (que solo tiene una carpeta llamada uclm).

En ambos casos le agrego la opción -l para que liste los detalles de cada directorio o archivo que encuentre.

Pero ¿cómo puedo saber qué opciones y argumentos acepta un comando?, bueno, hay generalmente dos formas de ver qué opciones y argumentos contempla un comando (aparte de buscarlo en google) sin salir de dentro del terminal.

  1. Las opciones de ayuda del propio comando (los mas habituales –help o -h)
  2. Usar el comando de ayuda en linea man. El comando man toma como argumento de entrada cualquier comando y te muestra la ayuda si está disponible en el computador

Un paseo por la consola de GNU/Linux

La consola de GNU/Linux es una herramienta muy útil para gestionar y administrar el sistema operativo. Es una herramienta que es imprescindible controlar por parte de cualquier profesional de la Informática. Vamos a ver en esta y en posteriores entradas cómo gestionar aspectos básicos del sistema operativo GNU/Linux, concretamente, usaremos una Debian 10.

La consola ejecuta un intérprete de comandos, esto es, un programa que acepta comandos y los ejecuta. El intérprete de comandos que vamos a usar se llama bash. La consola cuando se inicia nos da información de la máquina, usuario y directorio donde se encuentra. En la figura podemos ver cómo se presenta dicha información, generalmente «usuario@nombre-computador: Directorio donde estás$», despues del dolar ($) podemos empezar a poner órdenes en forma de comandos que se ejecutarán cuando presionemos intro. Esta información es configurable pero lo más habitual es mostrar esa información.

El usuario es el nombre del usuario que está ejecutando los comandos, es importante de cara a qué puede hacer y qué puede ejecutar (temas de permisos que veremos en otra entrada). El nombre del computador también es relevante, ya que la consola se usa para administrar de forma remota otros computadores y se necesita saber dónde estás ejecutando comandos. El directorio donde estás es importante para encontrar los archivos y las rutas relativas, que también veremos mas tarde. El simbolo ~ representa el directorio de trabajo del usuario, que en GNU/Linux está en /home/usuario.
Con esta información, en la figura, nos encontramos con que el usuario alumno, se encuentra en la máquina cuyo nombre es alumno-pc y en el directorio de trabajo suyo por defecto, es decir, el equivalente en Windows a C:\Usuarios\alumno.

Una vez iniciada la consola, podemos empezar a ejecutar comandos, por ejemplo, en la siguiente animación vemos como ejectuamos el comando pwd que le dice a la consola que imprima el directorio donde estas y luego ejecutamos el comando whoami que le dice a la consola que imprima el usuario con el que estamos logeados. Después de introducir el comando o la orden, debemos pulsar la tecla intro para que se ejecute.
Consola

Podemos ver la consola como la caja de herramientas por excelencia de los profesionales de la informática con un montón de herramientas (comandos) para multitud de propósitos. En próximas entradas vamos a ver cómo sentirnos cómodos en la consola explorando cómo ejecutar comandos con opciones, cómo encontrar archivos y las rutas de directorios, cómo ejectuar nuestros propios archivos (e.j archivos python) y gestionar permisos y cómo instalar nuevas herramientas.

Instalar el módulo LoRaWAN en NS3

LoRaWAN es una tecnología especialmente diseñada para IoT en dispositivos alimentados por baterías, con necesidades de comunicación bidireccional y un ancho de banda limitado (entre 0.3kbps y 50kbps). La topología por excelencia es la de estrella, con la pasarela en el centro de la estrella (que conecta la red LoRaWAN a una red IP convencional).

Existen tres clases de dispositivos en LoRaWAN (clase A, B, C) asociados a tres funcionalidades muy concretas. La clase A está asociado a un dispositivo final que periódicamente o bajo requerimientos de la aplicación, manda la información a la pasarela y espera una ventana de tiempo por si hubiera algún tipo de necesidad de comunicación en el sentido pasarela->dispositivo. Este esquema permite dormir el dispositivo de clase A entre transmisiones y obliga a guardar todas las comunicaciones en el sentido pasarela-> dispositivos y esperar a que se produzca una comunicación en el sentido dispositivo -> pasarela.

La clase B se sincroniza con la red y habilita un periodo de escucha de forma periódica (ventana máxima de 128 segundos) por si hubiera un mensaje hacia el dispositivo. De esta forma se puede hacer determinista cuanto tiempo tarda en recibir un mensaje de la red enviado por la pasarela. No obstante este despertar periódico consume batería y los hace menos eficiente que la clase A desde el punto de vista del consumo energético.

Finalmente, la clase C está siempre escuchando por lo que cualquier transmisión desde la red (pasarela) es inmediatamente recibido a menos que el propio dispositivo esté transmitiendo. Se pueden intercambiar los modos de funcionamiento entre la clase A y la clase C si necesitamos, por ejemplo, enviar una actualización de firmware a través de la interfaz inalámbrica.
En NS3 hay varias implementaciones de un módulo de NS3 para simular la tecnología LoRaWAN. El mas estable está disponible en la App Store de NS3.
La instalación es sencilla, pasa por bajarse el código en el directorio contrib de la instalación NS3 con git clone y la dirección https://github.com/signetlabdei/lorawan, y volver a configurar y compilar todo:

$ns-allinone-3.31/ns-3.31/contrib$ git clone https://github.com/signetlabdei/lorawan


Clonando en 'lorawan'...
remote: Enumerating objects: 37, done.
..
..
Resolviendo deltas: 100% (906/906), listo.

A continuación, en el directorio principal, la configuración y compilación habitual, habilitando los test y los ejemplos:

$ns-allinone-3.31/ns-3.31/$./waf configure --enable-test --enable-examples
Setting top to : /home/felix/tools/ns-allinone-3.31/ns-3.31
Setting out to : /home/felix/tools/ns-allinone-3.31/ns-3.31/build
Checking for 'gcc' (C compiler) : /usr/bin/gcc
..
..

a la hora de construir, fijarnos que el módulo lorawan aparece en los módulos compilados:

$/ns-allinone-3.31/ns-3.31$ ./waf build

[ 967/3001] Processing gen-module-header: ns3/core-module.h
[ 995/3001] Compiling src/core/model/unix-fd-reader.cc
[ 996/3001] Compiling src/core/model/system-thread.cc
[ 997/3001] Compiling src/core/model/wall-clock-synchronizer.cc
………….
Modules built:

lorawan (no Python)

No obstante, para comprobar que todo ha ido bien, podemos ejecutar sus test específicos del módulo:

$/ns-allinone-3.31/ns-3.31$./test.py -s lorawan
.....
[1/1] PASS: TestSuite lorawan
1 of 1 tests passed (1 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

y el ejemplo básico:

/ns-allinone-3.31/ns-3.31$ ./waf --run simple-network-example
Waf: Entering directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.166s)
SimpleLorawanNetworkExample:main(): Creating the channel...
+0.000000000s -1 SimpleLorawanNetworkExample:main(): Setting up helpers...
+0.000000000s -1 LoraPhyHelper:LoraPhyHelper(0x7ffe51c451a0)
...
+4.313600008s 0 EndDeviceLoraPhy:GetState()
+4.313600008s 0 EndDeviceLoraPhy:SwitchToSleep()
+4.313600008s 0 ClassAEndDeviceLorawanMac:CloseSecondReceiveWindow(): We have 8 transmissions left. We were not transmitting confirmed messages.
OneShotSender:~OneShotSender()

También hay un ejemplo de energía, que genera un archivo de texto battery-level.txt que guarda el nivel de batería para un dispositivo de clase A, a lo largo de una simulación:

/ns-allinone-3.31/ns-3.31$ ./waf --run energy-model-example
Waf: Entering directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.198s)
LoraEnergyModelExample:main(): Creating the channel...
+0.000000000s -1 LoraEnergyModelExample:main(): Setting up helpers...
+0.000000000s -1 LoraEnergyModelExample:main(): Creating the end device...
+0.000000000s -1 LoraEnergyModelExample:main(): Creating the gateway...

En futuras entradas, utilizaremos esta tecnología para ir creando una serie de escenarios de IoT con los que poder estudiar aspectos concretos.

Simulando consumo de energía en NS3

El consumo de energía es un parámetro importante sobre todo en escenarios de nodos alimentados por batería. Tener el perfil de consumo de un nodo no es tarea fácil y menos estimar su tiempo de vida conforme a unos parámetros de transmisión, encendido, apagado, etc.
Por ello hacer simulaciones de energía puede ser una buena práctica para muchos proyectos.

En NS3 se modela el consumo de energía haciendo una abstracción de los elementos que existen en la realidad. En este enlace puedes consultar la documentación oficial.

Los tres elementos principales son, las fuentes de energía, el modelo de energía de un dispositivo y un recolector de energía.

Una fuente de energía (Energy Source) es, como su propio nombre indica, una batería o elemento proveedor de energía que se conecta a un dispositivo para suministrarle energía de acuerdo a un modelo de energía de ese dispositivo.
Un modelo de energía de un dispositivo es un modelo de consumo de acuerdo a una serie de estados en los cuales un dispositivo puede estar. Por ejemplo, un dispositivo inalámbrico puede estar dormido, enviando o recibiendo información y tendrá un consumo distinto en función de cada uno de esos tres estados estado. Por lo tanto, debemos conectar ese modelo de energía con los estados del dispositivo.

Finalmente el recolector de energía sirve para modelar un dispositivo (e.g un panel solar) y el entorno (e.g. radiación solar) para proveer energía a una fuente de energía de cara a su recarga.

Cada uno de esos tres elementos se configuran mediante su correspondiente asistente (Energy source helper, Device Energy Model Helper y Energy Harvesting Helper) que te ayuda a configurar los parámetros básicos mediante atributos.

Los asistentes de modelos concretos heredan de esos asistentes con las particularidades de cada modelo/tecnología. Por ejemplo, hay un asistente para configurar una batería que hereda del Energy source helper y que añade los elementos específicos que caracterizan a una batería de ese tipo partiendo de los parámetros básicos del modelo:

  • Energía inicial(J)
  • Voltage de partida (V)
  • Tiempo de actualización del nivel de la batería

Por ejemplo, una batería del tipo RV añade parámetros como los valores de voltage de corte, valores alfa, beta, etc.
Existen modelos de batería de Litio Y rv.

De igual forma, el modelo de consumo de energía de un dispositivo se realiza asociándolo a una tecnología concreta, modelando cada uno de los estados y su consumo en Amperios. El modelo mas estudiado y modelado es el modelo wifi que define el consumo para hasta 7 estados distintos.
Para los dispositivos con varias interfaces (e.g. pasarelas con varias interfaces inalámbricas), una fuente de energía puede estar conectado a varios modelos de consumo.

Veamos el ejemplo básico que viene con la instalación de ns3, ejecutamos el ejemplo energy-model-example y vemos la salida que nos proporciona:

ns-allinone-3.31/ns-3.31$ ./waf --run energy-model-example
Waf: Entering directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.31/ns-3.31/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (2.409s)
+0.000000000s -1 Assign IP Addresses.
0.000574667s Current remaining energy = 0.0995293J
0.000574667s Total energy consumed by radio = 0.000470652J
0.000762667s Current remaining energy = 0.0993754J
0.000762667s Total energy consumed by radio = 0.000624624J
0.00287467s Current remaining energy = 0.0973922J
0.00287467s Total energy consumed by radio = 0.00260779J
--
Received one packet! Socket: 10.1.1.1 port: 49153 at time = 0.00287467
--
0.106594s Total energy consumed by radio = 0.0875537J
0.106594s Current remaining energy = 0.0124463J
End of simulation (10s) Total energy consumed by radio = 0.0982333J
End of simulation (10s) Total energy consumed by radio = 0.0875537J

en el archivo $/ns-3.31/examples/energy/energy-model-example.cc podemos ver ese ejemplo. Es un ejemplo muy básico wifi donde se configura, mediante asistentes, una fuente de energía y se asocia a un modelo de consumo de wifi.

Lo primero, creamos un asistente de fuentes de energía y lo configuramos con una energía inicial de 0.1 Julios.

BasicEnergySourceHelper basicSourceHelper;
basicSourceHelper.Set ("BasicEnergySourceInitialEnergyJ", DoubleValue (0.1));

Instalamos esa fuente de energía en todos los nodos de un contenedor previamente configurado (NodeContainer c) con los nodos wifi (2).

EnergySourceContainer sources = basicSourceHelper.Install (c);

A continuación configuramos el modelo de consumo que queremos para dichos dispositivos:

WifiRadioEnergyModelHelper radioEnergyHelper;
radioEnergyHelper.Set ("TxCurrentA", DoubleValue (0.0174));

Vemos que sólo configuramos el consumo cuando se transmite dejando el resto de consumos por defecto.
Finalmente, con las fuentes de energía y los modelos, creamos un contenedor asociando fuentes de energía y modelos de consumo:

DeviceEnergyModelContainer deviceModels = radioEnergyHelper.Install (devices, sources);

El resto de la configuración es exactamente igual a cualquier otro ejemplo de ns3 relacionado con WiFi.
Un aspecto a destacar es cómo se imprime la información de la energía restante en la batería. El mecanismo es algo diferente a lo visto en este tutorial y lo describiremos en detalle en otra entrada. Básicamente se asocia una función al cambio en el estado de la energía remanente de forma que cuando cambia esa variable se llama a esa función que imprime el valor.

Este mecanismo lo describiremos en profundidad en otra entrada.

Instalación del simulador NS3

La forma mas simple de instalación del simulador NS3 pasa por bajarse el paquete completo de la web de versiones del simulador y descomprimirla en el directorio de trabajo. El tutorial de instalación completo puede consultarse en la wiki de instalación. En mi sistema Debian Buster, tras bajarme el archivo ns-allinone-3.31.tar.bz2 se descomprime:

$ tar -xf ns-allinone-3.31.tar.bz2
$ ls
ns-allinone-3.31 ns-allinone-3.31.tar.bz2
$ cd ns-allinone-3.31/
ns-allinone-3.31$ ls
bake build.py constants.py netanim-3.108 ns-3.31 pybindgen-0.21.0 README util.py

Atendiendo al sistema operativo donde estés trabajando, necesitas instalar una serie de dependencias y librerías que ns3 utiliza para los diferentes módulos. Hay dependencias que no necesitas instalar si el módulo que depende de esas librerías no lo vas a utilizar. Si estás empezando instala todas las dependencias que se indican en el wiki de instalación para tu sistema operativo. También te indican para qué sirve cada dependencia, mi recomendación es que, como ya he dicho, si estás empezando, las instales todas. En mi caso, las dependencias para Debian Buster vienen especificadas en su sección correspondiente utilizando la herramienta apt-get.
Una vez instaladas las dependencias existen varias alternativas para la instalación. Puedes usar bake, bajarte el codigo fuente del repositorio e instalarlo de forma manual o utilizar el script build.py directamente en el caso del paquete completo. Utilizaremos este último método. En el directorio donde hemos descomprimido el archivo ns-allinone-3.31.tar.bz2, encontramos el archivo build.py. Lo ejecutamos para compilar el simulador:

ns-allinone-3.31$./build.py --enable-test --enable-examples

Se compilará todo el simulador en un proceso que puede tardar varios minutos. Las opciones habilitan los test y los ejemplos respectivamente, una opción que, si estás empezando, te servirán de guía en tus simulaciones. Si todo ha ido bien, debería informarse de los módulos compilados:

Modules built:
antenna aodv applications
bridge buildings config-store
core csma csma-layout
dsdv dsr energy
fd-net-device flow-monitor internet
internet-apps lr-wpan lte
mesh mobility netanim
network nix-vector-routing olsr
point-to-point point-to-point-layout propagation
sixlowpan spectrum stats
tap-bridge test (no Python) topology-read
traffic-control uan virtual-net-device
visualizer wave wifi
wimax

Modules not built (see ns-3 tutorial for explanation):
brite click mpi
openflow

Leaving directory `./ns-3.31'

Como último paso, comprobamos que todos los test pasan satisfactoriamente:

ns-allinone-3.31$cd ns-3.31
ns-allinone-3.31/ns-3.31$./test.py
..
..
657 of 660 tests passed (657 passed, 3 skipped, 0 failed, 0 crashed, 0 valgrind errors)
List of SKIPped tests:
ns3-tcp-cwnd (requires NSC)
ns3-tcp-interoperability (requires NSC)
nsc-tcp-loss (requires NSC)

y vemos que hemos pasado todos los test excepto tres relacionados con tcp que requieren de Network Simulation Cradle (NSC). Deberemos habilitar esa dependencia (y compilar todo) para pasar esos test.

De esta forma, ya tendríamos el simulador listo para trabajar y poder empezar a simular nuestros escenarios.

Otra posibilidad es trabajar directamente con la herramienta waf.Con la herramienta waf podemos realizar este proceso y visualizar qué componentes están habilitados y qué dependencias se encuentran disponibles o no.

$./waf configure

y compilar el proyecto con:

$./waf build

El sistema de trazas de NS3

Una vez que hemos creado una simulación necesitamos analizar los resultados de dicha simulación. Cómo sacar los resultados de una simulación es responsabilidad del sistema de trazas de NS3. El sistema de trazas de NS3 genera, como veremos a continuación, una serie de archivos en un formato determinado que nos muestra todos los resultados de nuestra simulación. Estos formatos están definidos en los módulos existentes en NS3 y podemos añadirlos a nuestros nuevos módulos. En esta entrada vamos a centrarnos en sacar información de los módulos existentes mediante un formato determinado (pcap).

Existen dos formatos que podemos generar, archivos con extensión .tr que es heredado del simulador NS2 y archivos con extensión .pcap que es un estándar para guardar paquetes de red. Por supuesto, al ser un archivo en C++ podemos generar resultados en otros formatos, pero supondría acceder a la información directamente lo cual es más complejo e ineficiente.

No obstante, personalmente recomiendo el uso de pcap, ya que es un estándar abierto soportado por multitud de herramientas que podemos utilizar para analizar los resultados de nuestra simulación. Por ejemplo tenemos wireshark si queremos analizar el diálogo de un protocolo en nuestra simulación o scapy si queremos generar gráficas específicas mediante scripts python.

Los módulos mas relevantes de NS3 ya tienen implementados un asistente para generar trazas. Estos asistentes o Helpers nos permiten generar de forma fácil las trazas más relevantes. Vamos a generar trazas de nuestro ejemplo básico para ver cómo funciona. Tomamos nuestro basic-iot-sensors.cc y vamos a generar un archivo pcap por cada interfaz creada de nuestros tres nodos (Recordar que un nodo puede tener varias interfaces).

En principio, con habilitar la traza pcap en la capa física de wifi nos vale con lo que nos basta añadir:

wifiPhy.EnablePcapAll("resultados");

Para generar un archivo resultados-NodeId-DeviceId.pcap. En nuestro caso tendríamos tres nodos con una única interfaz por lo que se generan los archivos resultados-0-0.pcap, resultados-1-0.pcap y resultados-2-0.pcap.
Captura wireshark

Todas las clases que heredan de la clase PcapHelperForDevice tiene este mecanismo para habilitar el pcap en todas las interfaces (Devices). Estas clases son (imagen extraída de la documentación de la clase PcapHelperForDevice):
Captura wireshark