Simulando consumo y recolección de energía con LoraWan en NS3 (y III)

Vamos a ver un ejemplo completo de simulación de consumo y recolección de energía con LoraWan en NS3. La idea es simular un nodo LoraWan al cual le añadiremos un módulo recolector de energía (BasicEnergyHarvesterHelper). El ejemplo que usaremos en esta entrada se encuentra en lorawan-energy.cc. Haremos especial énfasis en la parte de la energía. No obstante, vamos a ir describiendo paso a paso, cada bloque en el ejemplo.
Lo primero que tenemos es la creación de los nodos. Crearemos un dispositivo final en LoraWan y una pasarela. El ejemplo está preparado para ampliar sus capacidades por lo que podríamos poner el número de nodos finales que queramos como argumento. Tal y como vimos en la entrada relativa a los argumentos de entrada.
Volviendo a la creación de nodos:

NodeContainer endDevices;
endDevices.Create(numendDevices);
MobilityHelper scenario = createscenario(radio);
scenario.Install (endDevices);
//put the hub in 0.0 0.0 0.0 center
NodeContainer hub;
hub.Create(1);
MobilityHelper hubposition = createstarcenter();
hubposition.Install (hub);

Se crea un contenedor de nodos con numendDevices, por defecto igual a uno, y se posicionan con un radio igual a 20. Utilizamos para ello la función createscenario. A continuación creamos otro contenedor de nodos para los nodos que hacen las funciones de pasarela o gateway/hub. En este caso, también uno, y lo posicionamos en el centro. La función createstarcenter y la función createscenario se encuentran al final del archivo. Ambas devuelven un MobilityHelper que, utilizando los contenedores, instalamos en los nodos finales endDevices y en los hub.
A continuación debemos poner un interfaz LoraWan en cada dispositivo final y en cada Hub o pasarela.
Para ello, creamos un canal LoraWan de acuerdo a la documentación del módulo:

Ptr <logdistancepropagationlossmodel> loss = CreateObject<logdistancepropagationlossmodel> ();
loss->SetPathLossExponent (3.76);
loss->SetReference (1, 7.7);
Ptr<propagationdelaymodel> delay = CreateObject<constantspeedpropagationdelaymodel> ();
Ptr<lorachannel> channel = CreateObject<lorachannel> (loss, delay);

Estos parámetros de retardo y pérdida simulan la propagación de un canal usando la codificación LoRa. Ahora hay que crear las interfaces Lora, conectadas al canal y las añadiremos a los nodos ya creados. Primero la interfaz LoRa para los dispositivos finales usando los asistentes:

LoraPhyHelper phyHelper = LoraPhyHelper ();
phyHelper.SetChannel (channel);
LorawanMacHelper macHelper = LorawanMacHelper ();
LoraHelper helper = LoraHelper ();
helper.EnablePacketTracking();
phyHelper.SetDeviceType(LoraPhyHelper::ED);
macHelper.SetDeviceType(LorawanMacHelper::ED_A);
NetDeviceContainer endDevicesNetDevices = helper.Install(phyHelper, macHelper, endDevices);

Como se puede observar, creamos la capa física y MAC, indicándole que es un dispositivo final mediante ED y ED_A respectivamente. Adicionalmente, hacemos lo mismo con la pasarela.

LoraHelper helperHub = LoraHelper ();
phyHelper.SetDeviceType (LoraPhyHelper::GW);
macHelper.SetDeviceType (LorawanMacHelper::GW);
helperHub.Install (phyHelper, macHelper, hub);
macHelper.SetSpreadingFactorsUp (endDevices, hub, channel);

Ahora indicando que es una pasarela.
Bien, el siguiente paso es configurar una aplicación, que instalaremos en los nodos finales y que mandará un paquete cada 5 segundos de un tamaño de 12 bytes:

PeriodicSenderHelper periodicSenderHelper;
periodicSenderHelper.SetPeriod (Seconds (5));
periodicSenderHelper.SetPacketSize (12);
ApplicationContainer appContainer = periodicSenderHelper.Install (endDevices);
double simulationTime = 3600;
Time appStopTime = Seconds (simulationTime);
appContainer.Start (Seconds (0));
appContainer.Stop (appStopTime);

Como podemos ver, configuramos el periodo y el tamaño de paquete para instalarlo en los dispositivos finales. A continuación indicamos que inicie en el segundo 0 y termine a la hora de comienzo mediante una variable, appStopTime que utilizaremos para configurar la simulación.
El siguiente paso que vamos añadir a nuestro dispositivo final es una fuente de energía, una pila de 200 mAh y luego configuraremos el consumo de acuerdo a un circuito final. Primero la pila:

BasicEnergySourceHelper basicSourceHelper;
LoraRadioEnergyModelHelper radioEnergyHelper;


// Bateria PD2032 200 mAh 3.7V
basicSourceHelper.Set ("BasicEnergySourceInitialEnergyJ", DoubleValue (2664)); // Energy in J
basicSourceHelper.Set ("BasicEnergySupplyVoltageV", DoubleValue (3.7));

Como podemos ver, 2664 julios a 3.7 voltios.
El consumo lo obtenemos de un modem semtech sx1276:

radioEnergyHelper.Set ("StandbyCurrentA", DoubleValue (0.0016));
radioEnergyHelper.Set ("TxCurrentA", DoubleValue (0.120)); //20 dbm
radioEnergyHelper.Set ("SleepCurrentA", DoubleValue (0.0000002));
radioEnergyHelper.Set ("RxCurrentA", DoubleValue (0.0115));
radioEnergyHelper.SetTxCurrentModel ("ns3::ConstantLoraTxCurrentModel","TxCurrent", DoubleValue (0.12)); //+20dbm
EnergySourceContainer sources = basicSourceHelper.Install (endDevices);
DeviceEnergyModelContainer deviceModels = radioEnergyHelper.Install
(endDevicesNetDevices, sources);

Con esta configuración, nuestro dispositivo transmitiría hasta que terminara la simulación o hasta que la batería no suministrara suficiente energía.
Añadimos un simulador de un recolector de energía, que de forma periódica recolecta un valor aleatorio de energía:

BasicEnergyHarvesterHelper basicHarvesterHelper;
basicHarvesterHelper.Set ("PeriodicHarvestedPowerUpdateInterval", TimeValue (Seconds (1.0)));
basicHarvesterHelper.Set ("HarvestablePower", StringValue ("ns3::UniformRandomVariable[Min=0.0|Max=0.009]"));
EnergyHarvesterContainer harvesters = basicHarvesterHelper.Install (sources);

Podemos ver, que se configura una recolección periódica de un segundo con un valor aleatorio entre 0 y 0.09 julios.
Finalmente configuramos el navegador como en cualquier otra simulación:

Simulator::Stop (appStopTime);
Simulator::Run ();
Simulator::Destroy ();

El ejemplo tiene mas código relacionado con extraer resultados por línea de comando y por gnuplot, esta parte la veremos en otra entrada. Si ejecutamos el ejemplo, vemos que hay una serie de resultados relacionados con la energía restante en la pila.

$ ./waf --run lorawan-energy

Una vez ejecutada la simulación, tenemos un archivo gnuplot-energy-example.sh que se ha generado junto con los datos de la simulación, le damos permisos de ejecución, lo ejecutamos y nos genera una gráfica con la evolución de la energía restante en el nodo final durante la hora de simulación:
Energía restante con recolector de energía
Donde podemos ver cómo la recolección de energía mitiga el gasto energético asociado al envío de paquetes. Si vemos cómo sería la gráfica sin el recolector de energía, basta con comentar esa parte, volver a simular y obtener la gráfica:

Energía restante con recolector de energía

Donde podemos ver cómo la energía restante no se repone en ningún momento.
Es una buena técnica jugar con los valores para observar su influencia en la energía restante en la pila del dispositivo final.

Simulando consumo de energía en NS3 (II)

Seguimos explicando el modelo de simulación del consumo de energía. Ya explicamos en la primera entrada relativa a la energía los conceptos básicos modelados en NS3. Seguimos trabajando con el ejemplo básico examples/energy/energy-model-example.cc

Si observamos el ejemplo, existen dos funciones añadidas justo antes de la función principal, RemainingEnergy y TotalEnergy:

/// Trace function for remaining energy at node.
void
RemainingEnergy (double oldValue, double remainingEnergy)
{
NS_LOG_UNCOND (Simulator::Now ().GetSeconds ()
<< "s Current remaining energy = " << remainingEnergy << "J");
}

/// Trace function for total energy consumption at node.
void
TotalEnergy (double oldValue, double totalEnergy)
{
NS_LOG_UNCOND (Simulator::Now ().GetSeconds ()
<< "s Total energy consumed by radio = " << totalEnergy << "J");
}

Estas funciones, como podemos ver, imprimen la energía que queda en la fuente de energía y el total de energía consumida por la radio.

Para invocar estas funciones cuando existe un cambio en la energía restante y/o energía consumida, debemos enlazar las funciones anteriores a los cambios de estado de la fuente de energía y del modelo de energía consumido respectivamente. De esta forma, obtenemos primero una referencia a la fuente de energía con ID 1. A continuación, mediante la función TraceConnectWithoutContext, vinculamos el cambio de estado en RemainingEnergy a una llamada a la misma función, mediante MakeCallBack y un puntero a la función deseada, en este caso RemainingEnergy también:

Ptr basicSourcePtr = DynamicCast (sources.Get (1));
basicSourcePtr->TraceConnectWithoutContext ("RemainingEnergy", MakeCallback (&RemainingEnergy));

A continuación se sigue el mismo proceso obteniendo el modelo de consumo de energía de un dispositivo:

// device energy model
Ptr basicRadioModelPtr =
basicSourcePtr->FindDeviceEnergyModels ("ns3::WifiRadioEnergyModel").Get (0);
NS_ASSERT (basicRadioModelPtr != NULL);
basicRadioModelPtr->TraceConnectWithoutContext ("TotalEnergyConsumption", MakeCallback (&TotalEnergy));

De esta forma se tracean los cambios de estado en las fuentes de energía, que simulan baterías, modelos de energía, que simulan el consumo de las tarjetas de red, y los recolectores de energía que simularían paneles solares, viento, etc.

La función TraceconnectWithoutContext conecta una fuente de trazas (es decir, algo que representa un estado dentro de NS3 y que puede cambiar) con una función. Identificaremos estas fuentes de trazas por que son variables de la plantilla TracedValue, por ejemplo, puedes ver en la definición BasicEnergySource tiene una variable TracedValue m_remainingEnergyJ; que es susceptible de ser traceada mediante una función asociada con TraceconnectWithoutContext asocíandole una función que tenga como argumentos el valor antiguo y nuevo. El tipo de argumentos en este caso será double. La documentación de cada clase proporciona qué TracedSources tiene para usar este mecanismo (e.g. BasicEnergySource).

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. Un aspecto a tener en cuenta es que si instalas el módulo de lorawan, también existe un ejemplo de energía energy-model-example.cc por lo que waf puede ejecutar el ejemplo que se encuentre antes en el orden de búsqueda. Si quieres asegurarte ejecutar el ejemplo citado, pon la ruta completa ./waf --run examples/energy/energy-model-example

Bien, expliquemos el ejemplo 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.