2. Enhancing the 2-node TicToc

PREV: 1. Getting started UP: Contents

Step 2: Refining the graphics, and adding debugging output

Here we make the model look a bit prettier in the GUI. We assign the "block/routing" icon (the file images/block/routing.png), and paint it cyan for tic and yellow for toc. This is achieved by adding display strings to the NED file. The i= tag in the display string specifies the icon.

// "block/routing" icon to the simple module. All submodules of type
// Txc2 will use this icon by default
//
simple Txc2
{
    parameters:
        @display("i=block/routing"); // add a default icon
    gates:
        input in;
        output out;
}

//
// Make the two module look a bit different with colorization effect.
// Use cyan for `tic', and yellow for `toc'.
//
network Tictoc2
{
    submodules:
        tic: Txc2 {
            parameters:
                @display("i=,cyan"); // do not change the icon (first arg of i=) just colorize it
        }
        toc: Txc2 {
            parameters:
                @display("i=,gold"); // here too
        }
    connections:

You can see the result here:

step2a.png

We also modify the C++ file to add some debug messages to Txc1 by writing to the OMNeT++ EV object like this:

        EV << "Sending initial message\n";

and

    EV << "Received message `" << msg->getName() << "', sending it out again\n";

When you run the simulation in the OMNeT++ GUI Tkenv, the following output will appear in the main text window:

step2b.png

You can also open separate output windows for tic and toc by right-clicking on their icons and choosing Module output from the menu. This feature will be useful when you have a large model ("fast scrolling logs syndrome") and you're interested only in the log messages of specific module.

step2c.png

Sources: tictoc2.ned, txc2.cc, omnetpp.ini

Step 3: Adding state variables

In this step we add a counter to the module, and delete the message after ten exchanges.

We add the counter as a class member:

class Txc3 : public cSimpleModule
{
  private:
    int counter;  // Note the counter here

  protected:

We set the variable to 10 in initialize() and decrement in handleMessage(), that is, on every message arrival. After it reaches zero, the simulation will run out of events and terminate.

Note the

    WATCH(counter);

line in the source: this makes it possible to see the counter value in Tkenv. Double-click on tic's icon, then choose the Contents page from the inspector window that pops up.

step3.png

As you continue running the simulation, you can follow as the counter keeps decrementing until it reaches zero.

Sources: tictoc3.ned, txc3.cc, omnetpp.ini

Step 4: Adding parameters

In this step you'll learn how to add input parameters to the simulation: we'll turn the "magic number" 10 into a parameter and add a boolean parameter to decide whether the module should send out the first message in its initialization code (whether this is a tic or a toc module).

Module parameters have to be declared in the NED file. The data type can be numeric, string, bool, or xml (the latter is for easy access to XML config files), among others.

simple Txc4
{
    parameters:
        bool sendMsgOnInit = default(false); // whether the module should send out a message on initialization
        int limit = default(2);   // another parameter with a default value
        @display("i=block/routing");
    gates:

We also have to modify the C++ code to read the parameter in initialize(), and assign it to the counter.

    counter = par("limit");

We can use the second parameter to decide whether to send initial message:

    if (par("sendMsgOnInit").boolValue() == true)

Now, we can assign the parameters in the NED file or from omnetpp.ini. Assignments in the NED file take precedence. You can define default values for parameters if you use the default(...) syntax in the NED file. In this case you can either set the value of the parameter in omnetpp.ini or use the default value provided by the NED file.

Here, we assign one parameter in the NED file:

network Tictoc4
{
    submodules:
        tic: Txc4 {
            parameters:
                sendMsgOnInit = true;
                @display("i=,cyan");
        }
        toc: Txc4 {
            parameters:
                sendMsgOnInit = false;
                @display("i=,gold");
        }
    connections:

and the other in omnetpp.ini:

Tictoc4.toc.limit = 5

Note that because omnetpp.ini supports wildcards, and parameters assigned from NED files take precedence over the ones in omnetpp.ini, we could have used

Tictoc4.t*c.limit=5

or

Tictoc4.*.limit=5

or even

**.limit=5

with the same effect. (The difference between * and ** is that * will not match a dot and ** will.)

In Tkenv, you can inspect module parameters either in the object tree on the left-hand side of the main window, or in the Parameters page of the module inspector (opened via double-clicking on the module icon).

The module with the smaller limit will delete the message and thereby conclude the simulation.

Sources: tictoc4.ned, txc4.cc, omnetpp.ini

Step 5: Using inheritance

If we take a closer look at the NED file we will realize that tic and toc differs only in their parameter values and their display string. We can create a new simple module type by inheriting from an other one and specifying or overriding some of its parameters. In our case we will derive two simple module types (Tic and Toc). Later we can use these types when defining the submodules in the network.

Deriving from an existing simple module is easy. Here is the base module:

simple Txc5
{
    parameters:
        bool sendMsgOnInit = default(false);
        int limit = default(2);
        @display("i=block/routing");
    gates:
        input in;
        output out;
}

And here is the derived module. We just simply specify the parameter values and add some display properties.

simple Tic5 extends Txc5
{
    parameters:
        @display("i=,cyan");
        sendMsgOnInit = true;   // Tic modules should send a message on init
}

The Toc module looks similar, but with different parameter values.

simple Toc5 extends Txc5
{
    parameters:
        @display("i=,gold");
        sendMsgOnInit = false;  // Toc modules should NOT send a message on init
}

Note:
The C++ implementation is inherited from the base simple module (Txc4).

Once we created the new simple modules, we can use them as submodule types in our network:

network Tictoc5
{
    submodules:
        tic: Tic5;  // the limit parameter is still unbound here. We will get it from the ini file
        toc: Toc5;
    connections:

As you can see, the network definition is much shorter and simpler now. Inheritance allows you to use common types in your network and avoid redundant definitions and parameter settings.

Step 6: Modeling processing delay

In the previous models, tic and toc immediately sent back the received message. Here we'll add some timing: tic and toc will hold the message for 1 simulated second before sending it back. In OMNeT++ such timing is achieved by the module sending a message to itself. Such messages are called self-messages (but only because of the way they are used, otherwise they are ordinary message objects).

We added two cMessage * variables, event and tictocMsg to the class, to remember the message we use for timing and message whose processing delay we are simulating.

class Txc6 : public cSimpleModule
{
  private:
    cMessage *event; // pointer to the event object which we'll use for timing
    cMessage *tictocMsg; // variable to remember the message until we send it back

  public:

We "send" the self-messages with the scheduleAt() function, specifying when it should be delivered back to the module.

        scheduleAt(simTime()+1.0, event);

In handleMessage() now we have to differentiate whether a new message has arrived via the input gate or the self-message came back (timer expired). Here we are using

    if (msg==event)

but we could have written

    if (msg->isSelfMessage())

as well.

We have left out the counter, to keep the source code small.

The result of running the simulation can be seen below.

step6.png

Sources: tictoc6.ned, txc6.cc, omnetpp.ini

Step 7: Random numbers and parameters

In this step we'll introduce random numbers. We change the delay from 1s to a random value which can be set from the NED file or from omnetpp.ini. Module parameters are able to return random variables; however, to make use of this feature we have to read the parameter in handleMessage() every time we use it.

            // The "delayTime" module parameter can be set to values like
            // "exponential(5)" (tictoc7.ned, omnetpp.ini), and then here
            // we'll get a different delay every time.
            simtime_t delay = par("delayTime");

            EV << "Message arrived, starting to wait " << delay << " secs...\n";
            tictocMsg = msg;
            scheduleAt(simTime()+delay, event);

In addition, we'll "lose" (delete) the packet with a small (hardcoded) probability.

        if (uniform(0,1) < 0.1)
        {
            EV << "\"Losing\" message\n";
            delete msg;
        }

We'll assign the parameters in omnetpp.ini:

Tictoc7.tic.delayTime = exponential(3s)
Tictoc7.toc.delayTime = truncnormal(3s,1s)

You can try that no matter how many times you re-run the simulation (or restart it, Simulate|Rebuild network menu item), you'll get exactly the same results. This is because OMNeT++ uses a deterministic algorithm (by default the Mersenne Twister RNG) to generate random numbers, and initializes it to the same seed. This is important for reproducible simulations. You can experiment with different seeds if you add the following lines to omnetpp.ini:

[General]
seed-0-mt=532569  # or any other 32-bit value

From the syntax you have probably guessed that OMNeT++ supports more than one RNGs. That's right, however, all models in this tutorial use RNG 0.

Exercise: Try other distributions as well.

Sources: tictoc8.ned, txc7.cc, omnetpp.ini

Step 8: Timeout, cancelling timers

In order to get one step closer to modelling networking protocols, let us transform our model into a stop-and-wait simulation. This time we'll have separate classes for tic and toc. The basic scenario is similar to the previous ones: tic and toc will be tossing a message to one another. However, toc will "lose" the message with some nonzero probability, and in that case tic will have to resend it.

Here's toc's code:

void Toc8::handleMessage(cMessage *msg)
{
    if (uniform(0,1) < 0.1)
    {
        EV << "\"Losing\" message.\n";
        bubble("message lost");  // making animation more informative...
        delete msg;
    }
    else

Thanks to the bubble() call in the code, toc'll display a callout whenever it drops the message.

step8.png

So, tic will start a timer whenever it sends the message. When the timer expires, we'll assume the message was lost and send another one. If toc's reply arrives, the timer has to be cancelled. The timer will be (what else?) a self-message.

        scheduleAt(simTime()+timeout, timeoutEvent);

Cancelling the timer will be done with the cancelEvent() call. Note that this does not prevent us from being able to reuse the same timeout message over and over.

        cancelEvent(timeoutEvent);

You can read Tic's full source in txc8.cc.

Sources: tictoc8.ned, txc8.cc, omnetpp.ini

Step 9: Retransmitting the same message

In this step we refine the previous model. There we just created another packet if we needed to retransmit. This is OK because the packet didn't contain much, but in real life it's usually more practical to keep a copy of the original packet so that we can re-send it without the need to build it again.

What we do here is keep the original packet and send only copies of it. We delete the original when toc's acknowledgement arrives. To make it easier to visually verify the model, we'll include a message sequence number the message names.

In order to avoid handleMessage() growing too large, we'll put the corresponding code into two new functions, generateNewMessage() and sendCopyOf() and call them from handleMessage().

The functions:

cMessage *Tic9::generateNewMessage()
{
    // Generate a message with a different name every time.
    char msgname[20];
    sprintf(msgname, "tic-%d", ++seq);
    cMessage *msg = new cMessage(msgname);
    return msg;
}

void Tic9::sendCopyOf(cMessage *msg)
{
    // Duplicate message and send the copy.
    cMessage *copy = (cMessage *) msg->dup();
    send(copy, "out");
}

Sources: tictoc9.ned, txc9.cc, omnetpp.ini

NEXT: 3. Turning it into a real network

Generated on Fri Mar 7 17:19:44 2014 for Tictoc Tutorial by  doxygen 1.6.3