hellocorelua

HelloCoreLua

This tutorial will guide you through the basic concepts and programming involved in the communication between a mobile node (MN) in Lua and a stationary node within the SDDL core network architecture. The tutorial focus will be on the Lua client, precisely explaining how to connect, send, and receive messages to and from a SDDL core. Like in the HelloCore tutorial in Java, the stationary node of the SDDL core will play the role of a server processing node, capable of processing application messages from the Lua mobile node (MN).

One of the key differences between programming in Java and Lua for SDDL is the need to define the exchanged messages in a language-independent format, such as XDR, JSON, Google Protocol Buffers, etc. In this sample, we opted for JSON to express the messages changed between the client and server. Note that both instances - client and server - need to serialize their message in this format.

Our sample application is composed of two components: a lua mobile client node and a stationary java node, as illustrated in the figure on the right. The MN creates a message, containing a random latitude, longitude and a custom id (expressed in uuid), which is sent to the SDDL core. Our processing node receives this message and execute a simple logic, keeps track on the number of messages received and prints them on the screen. To exemplify the other path of communication, the processing node sends a reply message to the MN containing the number of message that it received so far.

The tutorial will focus on the code of the lua mobile client application, and then continues with the discussion of the adaptation of the processing node's code. As a matter of simplification, the entire application system, composed of the mobile client, gateway, and processing node, runs locally on a single machine, but it can be easily modified to run on distributed machines.

You do not need to copy the source code snippets shown in this tutorial. At the end of each section there is a link to download the entire source code of the section. Moreover, all examples are included in the Lua clientlib client distribution.

To create our Lua mobile client application, create a Lua program called helloclient.lua. First of all, you will need to import the ClientLib libraries, which are the following:

-- clientlib requires
local logging    = require("logging")
local dispatcher = require("lac.dispatcher")
local node       = require("lac.clientlib.mrudp.node")
local messages   = require("lac.clientlib.messages")

In addition to these libraries, we will also import a set of Lua libraries to send messages in a language-independent format. For this sample, we choose JSON to describe the exchanged data because it is very flexible and has a direct mapping to Lua and Java. Note that the developer is free to choose any language-independent format to exchange messages between the mobile client and the processing node. The only requirement is that the both need to be able to understand the exchanged bytes.

-- app specific requires
local math       = require("math")
local uuid       = require("uuid")
local json       = require("dkjson")

In SDDL, nodes that are outside the core (either mobile or stationary) interact directly with the Gateway rather than the processing nodes, since it uses a different protocol (ClientLib and MRUDP) than the core (Data Distribution Service (DDS)). The Gateway translate the node message to the core protocol and vice-versa. Thus, the primary interaction of the client is with the gateway. Therefore, we first declare two variables to locate our gateway, its IP address and port number. Since in this tutorial we are running our entire application (mobile client node, gateway, and processing node) locally, we can specify the gateway IP to the loopback address 127.0.0.1 and choose its default port 5500. In a real distributed scenario, the client would change these variables to the gateways IP and port respectively.

-- server info
local gw_ip    = "127.0.0.1"
local gw_port  = 5500

After that, we specify our logging information. The logger shows ClientLib debug information; it is very useful if something goes wrong. Since we are doing a basic application, we will instruct the logger to show only messages that are equal or above FATAL level (more on logging level here). The dispatcher act as a “thread” manager in Lua. It uses coroutines to switch context between sending, receiving, and other internal function tasks. We only need to create a dispatcher, this entire logic is hidden from the developer.

-- logger
logging.console = require("logging.console")
 
-- dispatcher
local disp      = dispatcher.new()

After that, we specify a listener for our Lua MN client. The ClientLib Lua listener is a table that contains attributes that are functions. These functions are triggered by the library when an event happens, such as when the client receives a message, connects, disconnects or reconnects into a gateway. They receive the MN connection as argument and the message received or unsent. We will focus on the new_message_received function. Our function prints in the screen the received message and the size of it (in bytes). We expected that the server sended a message in a JSON format so we decode the message bytes into a JSON table. Finally, our receive message function prints the key and values of the JSON table that we decoded.

Note that, we can also prefix the listeners functions arguments with a custom object, in this case we prefix with a prefix_variable. Prefix variables can be specified when we attach the listener to the connection, as we will explain later in the tutorial. By doing that, in every function call of our listener the middleware will pass the prefix argument along with the default arguments (node and msg). This is useful if you want to access a custom variable inside the listener functions. The code for the listener is displayed below:

-- listener
local listener = {
  new_message_received = function(prefix_variable, node, msg)
    print("Message Received!", node, msg)
    print("got a JSON object with ".. #msg.content .." bytes")
 
	local json_content, pos, err = json.decode (msg.content, 1, nil)	
    for k, v in pairs(json_content) do
      print(k, v)
    end
  end,
 
  unsent_messages = function(prefix_variable, node, msgs)
    print("Unsent messages" .. #msgs)  
  end,
 
  connected = function(node)
    print("Connected")
  end,
 
  reconnected = function(node)
    print("Reconnected")
  end,
 
  disconnected = function(node)
    print("Disconnected")
  end,
}

Our client will send a table containing a random latitude, longitude and UUID. The following data displays this table:

-- content
local client_data = {
  lat  = -1 * math.random(22, 25),
  lng  = -1 * math.random(31, 33),
  uuid = uuid.new()
}

The data will be sent repeatedly by our client application to the Java server, which is the main logic of our program. We will specify the entry routine of our Lua mobile client in a function called main. This function will be passed as argument to the dispatcher, which in turn start the execution of the routine.

First we instantiate the logging feature and the minimum logging level to FATAL. After that, we start the connection between the client and a gateway. The client connection is very straightforward; first, we create a connection using the node.new() function. The first argument is a scheduler (dispatcher), the second is an existing socket, the third is the sender UUID, the fourth are the connection configuration parameters, and the last is the logger table. The function definition is:

node.new(scheduler, skt, sender_uuid, cfg, logger)

The connection table, encapsulate the details and protocol involved in the physical connection with the gateway, for instance, it uses unique ids to identify the client and the connection when the node changes IPs. If we do not specify an argument in the connection instantiation, it automatically generates an UUID to identify the client. As you have read in the basic concepts, this id is used to unique identify the client independent of his IP, which is useful for handover and abstracting the client location.

After creating the node connection, we establish the connection calling the connect() method passing the gateway address and port as parameter. Finally, we attach our listener in the connection, this listener holds methods related to the node gateway connection. It will trigger functions to indicate that the node has successfully connected, disconnected, reconnected from the gateway alongside with operations to receive new messages, and unsent messages (messages that were timeouted and not sent due to disconnection).

The routine has a main loop that executes forever. We attribute random values to our client_data content. This content is encoded into a JSON so that we can communicate with the Java server. We create an application message, using the message.new() function. Like in the Java client, we set the application message content using the set_content_lua_obj() and specify that the content is encoded in JSON. The other formats available are: Java, Protocol Buffers and XML format. We send the message using the send_message(appmsg) in the connection table. Finally, we attach the main routine to the dispatcher.

The entire code of our lua client is displayed below:

helloclient.lua
--[[
    Hello Core Lua Client
    Simple lua client that sends every 10s a table containig a random lat, lng and a custom ID (uuid).
--]]
 
-- clientlib requires
local logging    = require("logging")
local dispatcher = require("lac.dispatcher")
local node       = require("lac.clientlib.mrudp.node")
local messages   = require("lac.clientlib.messages")
 
-- app specific requires
local math       = require("math")
local uuid       = require("uuid")
local json       = require("dkjson")
 
-- server info
local gw_ip    = "127.0.0.1"
local gw_port  = 5500
 
-- logger
logging.console = require("logging.console")
 
-- dispatcher
local disp     = dispatcher.new()
 
-- listener
local listener = {
  new_message_received = function(prefix_variable, node, msg)
    print("Message Received!", node, msg)
    print("got a JSON object with ".. #msg.content .." bytes")
 
	local json_content, pos, err = json.decode (msg.content, 1, nil)	
    for k, v in pairs(json_content) do
      print(k, v)
    end
  end,
 
  unsent_messages = function(prefix_variable, node, msgs)
    print("Unsent messages" .. #msgs)  
  end,
 
  connected = function(prefix_variable, node)
    print("Connected")
  end,
 
  reconnected = function(prefix_variable, node)
    print("Reconnected")
  end,
 
  disconnected = function(prefix_variable, node)
    print("Disconnected")
  end,
}
 
-- content
local client_data = {
  lat  = -1 * math.random(22, 25),
  lng  = -1 * math.random(31, 33),
  uuid = uuid.new()
}
 
function main()
  local logger = logging.console()
  logger:setLevel(logging.FATAL)
 
 
  local connection = node.new(disp, nil, client_data.uuid, nil, logger)
  connection:connect(gw_ip, gw_port)
 
  -- we could pass a prefix variable, e.g.:
  -- add_listener("external", listener, prefix_variable, prefix_variable2, ...)
  connection:add_listener("external", listener) 
 
  while true do
    -- random lat and lng and encode in json
    client_data.lat = -1 * math.random(22, 25)
    client_data.lng = -1 * math.random(31, 33)
    local lua_content_in_json = json.encode(client_data, {indent = true})
 
    local appMSG = messages.new()
    appMSG.content_type = messages.content_types.JSON
    appMSG:set_content_lua_obj(lua_content_in_json)
    connection:send_message(appMSG)
 
    disp:sleep(20)
  end
 
  disp:sleep(1)
  connection:disconnect()
 
  disp:sleep(1)
end
 
disp:start(main)

The core nodes establishes premises and protocols that are not suitable for mobile applications. To expand this processing power to mobile nodes, we added the concept of gateways that use our lightweight protocol to communicate with mobile nodes and translate their message to core messages. Developers can instantiate nodes to process the mobile nodes message.

To create our processing node application, create an empty Java HelloCoreServer Class using the br.pucrio.inf.lac.helloworld package. You can create this class in the existing HelloCore project or create a separated project, for now we will make the class in the existing project. The processing nodes communicate using SDDL, an abstraction that encapsulate the DDS communication.

The main difference between the Processing Node of this version and the Java Hello Core Server is the addition of the transformation of the response to JSON. We will send the number of messages and an attribute country to the client. The entire code of our server is displayed below. The difference exists on the onNewData method.

HelloCoreServer.java
package br.pucrio.inf.lac.helloworld;
 
import java.util.logging.Level;
import java.util.logging.Logger;
 
import org.json.simple.JSONObject;
 
import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.serialization.Serialization;
import lac.cnet.sddl.objects.ApplicationObject;
import lac.cnet.sddl.objects.Message;
import lac.cnet.sddl.objects.PrivateMessage;
import lac.cnet.sddl.udi.core.SddlLayer;
import lac.cnet.sddl.udi.core.UniversalDDSLayerFactory;
import lac.cnet.sddl.udi.core.listener.UDIDataReaderListener;
 
public class HelloCoreServer implements UDIDataReaderListener<ApplicationObject> {
  SddlLayer  core;
  int        counter;
 
  public static void main(String[] args) {
    Logger.getLogger("").setLevel(Level.OFF);
 
    new HelloCoreServer();
  }
 
  public HelloCoreServer() {
    core = UniversalDDSLayerFactory.getInstance();
    core.createParticipant(UniversalDDSLayerFactory.CNET_DOMAIN);
 
    core.createPublisher();
    core.createSubscriber();
 
    Object receiveMessageTopic = core.createTopic(Message.class, Message.class.getSimpleName());
    core.createDataReader(this, receiveMessageTopic);
 
    Object toMobileNodeTopic = core.createTopic(PrivateMessage.class, PrivateMessage.class.getSimpleName());
    core.createDataWriter(toMobileNodeTopic);
 
    counter = 0;
    synchronized (this) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
 
  @Override
  public void onNewData(ApplicationObject topicSample) {
    Message message = (Message) topicSample;
    System.out.println(Serialization.fromJavaByteStream(message.getContent()));
 
    PrivateMessage privateMessage = new PrivateMessage();
    privateMessage.setGatewayId(message.getGatewayId());
    privateMessage.setNodeId(message.getSenderId());
 
    synchronized (core) {
       JSONObject jsonObject      = new JSONObject();
       jsonObject.put("counter", counter);
       jsonObject.put("country", "Brazil");
    }
 
    String content = jsonObject.toJSONString();
    ApplicationMessage appMsg = new ApplicationMessage();
    appMsg.setContentObject(content);
 
    privateMessage.setMessage(Serialization.toProtocolMessage(appMsg));
    core.writeTopic(PrivateMessage.class.getSimpleName(), privateMessage);
  }
 
}

To execute our application, we need to first create the gateway, which will instantiate the core infrastructure if none exists. The gateway receives two parameters, its public IP and a given port number. Since we are this sample locally, we can choose the loopback IP 127.0.0.1 and the default middleware port 5500. To do that, open a shell, and run the following command:

$ gateway 127.0.0.1 5500

After that, we instantiate our processing node. To do that, run the HelloCoreServer class in Eclipse as a Java application. Finally, execute the mobile client running the lua command line interpreter:

$ lua helloclient.lua
  • hellocorelua.txt
  • Last modified: 2017/07/21 03:08
  • (external edit)