Skip to content

JavaMadaraArchitecture

James Edmondson edited this page Jul 7, 2018 · 4 revisions

Java Guide Series
Architecture | Knowledge Base | Networking | Containers | Threads


The MADARA Architecture

The MADARA architecture provides powerful tools for control, timing, and reasoning for distributed networks of devices, services, and applications. The following image outlines the types of interactions available through user configurations or interactions with MADARA functional entities. Each number in the diagram matches a corresponding numbered description below.

` ----

Table of Contents


1. Updating/retrieving knowledge

The main way that users interact with the MADARA system is by retrieving or mutating state from their Java programs. There are four main functions provided by MADARA to developers for accessing state information. We detail these functions below, but you can see a full list of these functions in the developer documentation for the KnowledgeBase class.

Evaluate

Example

import ai.madara.knowledge.KnowledgeBase;

//create a knowledge base with no transport
KnowledgeBase knowledge = new KnowledgeBase ();

//check if agents are ready, and if so, set starting_maneuver to 1,
//and call a function called perform_maneuver
knowledge.evaluate("agents.ready => (starting_maneuver = 1; perform_maneuver())");

Evaluate is an one-shot evaluation of Knowledge and Reasoning Language (KaRL) logic. KaRL logic allows for various conditionals, predicates, and mutations of variables in the knowledge base.

Evaluate has two function signatures, one of which takes a string KaRL logic and the other which takes a compiled KaRL logic, which is preferred for real-time systems and production code.

The above example checks if someone has set agents.ready to true (non-zero) and if so, we set starting_maneuver to 1 and then call a function called perform_maneuver, which can be mapped to our own Python function that moves a robotic arm, moves to a location, or some other appropriate action.

Get

Example

import ai.madara.knowledge.KnowledgeBase;

//create a knowledge base with no transport
KnowledgeBase knowledge = new KnowledgeBase ();

//check if the value of all_agents_ready is equal to 1
if (knowledge.get("all_agents_ready").toLongValue() 1)
{
    //all_agents_ready is equal to 1. Do some logic here.
    ...
}

Developers who want to directly access a single variable from the knowledge base, they can use the get function. Get is an atomic call to the thread-safe context that returns the value of a variable. Values can be strings, 64-bit integers, doubles, arrays of integers or doubles, XML, text files, images, and arbitrary byte arrays or files. The value returned is a knowledge record, which allows for explicit conversions to other types via toStringValue(), toLongValue(), toDoubleValue(), and similar functions.

Set

Example

import ai.madara.knowledge.KnowledgeBase;

//create a knowledge base with no transport
KnowledgeBase knowledge = new KnowledgeBase ();

//set the variable all_agents_ready to 1.0 (double)
knowledge.set("all_agents_ready", 1.0);

Developers can use set to change the value of a variable in the knowledge base to a double, integer, string, byte array, image, XML file, or some other supported type. If the variable begins with a ".", like ".i", the variable is considered local and is changes are not sent to the transport layer. If the variable begins with a non-period, like "agents.ready", then the value is sent through the transport layer to all agents who may be interested in the information.

Get and set paired together can be used by developers to emulate a high performance no-SQL database with nanosecond mutation operations and microsecond overhead.

The above example would set the global variable "all_agents_ready" to double value 1.0 and then send that update over the transport.

For a more detailed guide on using the Knowledge Base, see JavaInteractingWithTheKnowledgeBase.

Wait

Example

import ai.madara.knowledge.KnowledgeBase;

//create a knowledge base with no transport
KnowledgeBase knowledge = new KnowledgeBase ();

//wait for the value of all_agents_ready to be non-zero
knowledge.wait ("all_agents_ready");

Developers can use the wait function to wait on some important predicate before proceeding to other operations in their Java code. Like evaluate, wait can take a user-provided stringified KaRL logic or a compiled version of that logic, which will save lookup/compilation time within the KaRL interpreter. The wait statement can be configured to poll at a certain interval and have a maximum wait time to control how the logic is evaluated and for how long.

The above example waits on the global variable all_agents_ready to be not 0.


2. Sending/receiving knowledge

The second interaction that affects users of MADARA is the sending and receiving of knowledge between reasoning agents, generally over a network. Sending/receiving is done by mutating global variables with the evaluate, set, or wait functions outlined in the previous section.

Example

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.transport.TransportSettings;
import ai.madara.transport.TransportType;

//Create transport settings for a multicast transport
TransportSettings settings = new TransportSettings();
settings.setHosts(new String[]{"239.255.0.1:4150"});
settings.setType(TransportType.MULTICAST_TRANSPORT);

//create a knowledge base with the multicast transport settings
KnowledgeBase knowledge = new KnowledgeBase("agent1", settings);

//send all agents listening on this multicast address the knowledge
//that all agents are currently ready
knowledge.evaluate("all_agents_ready = 1");

As of version 1.3.3, MADARA currently supports five transports: UDP, IP Multicast, IP Broadcast, Open Splice DDS, and RTI DDS. MADARA transports are not reliable by default because reliability is expensive, and because most real-time systems do not wish to have blocking semantics for reliable transfer of packets. However, even though reliability is not enforced at the transport layer for these three transports, you can wait for acks for important information using evaluate and wait calls to provide reliable delivery of important information that must be received by one or more entities in the network.

Sophisticated example showcasing reliable delivery, despite unreliable transport. This essentially builds a barrier that waits for all agents to acknowledge each other's readiness.

import ai.madara.knowledge.KnowledgeBase;
import ai.madara.transport.TransportSettings;
import ai.madara.transport.TransportType;

//Create transport settings for a multicast transport
TransportSettings settings = new TransportSettings();
settings.setHosts(new String[]{"239.255.0.1:4150"});
settings.setType(TransportType.MULTICAST_TRANSPORT);

//create a knowledge base with the multicast transport settings
KnowledgeBase knowledge = new KnowledgeBase("agent1", settings);

//set our unique number id
knowledge.set(".id", id_from_command_line);

# set the number of agents in the network
knowledge.set(".agents", number_of_agents_from_command_line);

//wait for all agents to reply back that they understand we are ready
knowledge.wait (
    //keep track of the acks others have sent us for our own readiness
    //";>" indicates the wait should look to the right for the truth condition
    "agent{.id}.acks = 0 ;>" +

    //agents finished is used for our truth condition later.
    //Keep looking right in the expression tree for the wait truth condition.
    ".agents_finished = 0 ;>" + 

    //indicate that this agent is ready. Keep looking right for condition.
    "agent{.id}.ready = 1 ;>" + 

    //for loop on variable i from 0 to .agents to check our acks and send acks
    ".i [0->.agents) (" + 
    //count the number of acks to our ready message
    "  agent{.id}.{.i}.ack => ++agent{.id}.acks;" +
    //send an ack to the agent who is ready
    "  agent{.i}.ready => agent{.i}.{.id}.ack = 1" +
    //end the for loop and keep looking right for truth condition for wait statement
    ") ;>" +

    //for loop on variable i from 0 to .agents to check if everyone has received all necessary information
    ".i [0->.agents) (" +
    //check if the agent has received all their acks and mark them as finished
    "  agent{.i}.acks .agents => ++.agents_finished" +
    //end the for loop and keep looking right for truth condition for wait statement
    ") ;>" +

    //truth condition for wait statement
    ".agents_finished .agents"
);

Two transports (Open Splice DDS and RTI DDS) do provide "reliable" delivery, but each has different semantics for what happens when queue lengths run out (e.g. due to overpublishing, disconnected operations between agents, etc.) You should seek the documentation of these transports' "reliable" transports to get a better grasp of what that means. In general, on a wireless network, reliability is a gradient of support and not an absolute guarantee of arrival. However, both DDS implementations will block your application if you send too many packets during a disconnected operation. For this reason, most real-time systems are built to prioritize the most recent information over stale resends and to only use acks on important information and not every single piece of information sent over the network.

Transports are configured with the TransportSettings class. This class is passed to the KnowledgeBase class constructor or through the attach methods, available via the KnowledgeBase class.


3. Logging

log_level

Example

import ai.madara.logger.GlobalLogger;
import ai.madara.logger.LogLevels;

//Set the log level to 10 (MADARA_LOG_DETAILED_TRACE)
GlobalLogger.setLevel(LogLevels.LOG_DETAILED);
//Or
GlobalLogger.SetLevel(LogLevels.LOG_DETAILED.value ());

The log level is an integer, generally from 0 to 10, that indicates what level of logging the user wants to see. Passing no parameters to the function will return the current logging level.

Setting log level will affect all MADARA operations after that point until you change the log level again (e.g., if you set log level to 7 that will be the log level for all MADARA operations after that point). These are static functions so they are applied across all knowledge bases in the local process. Changing log level does not send additional information across the network.


4. Calling native user functions

Example

import ai.madara.*;

# user-defined Java function to call from MADARA

//for this example, we're not using a transport
KnowledgeBase knowledge = new KnowledgeBase();

knowledge.defineFunction("move", new MadaraFunction()
{
    @Override
    public KnowledgeRecord execute(KnowledgeList args, MadaraVariables variables)
    {
        //TODO: Perform my movement operation
        return new KnowledgeRecord(0);
    }
});

knowledge.evaluate("move()");

MADARA is intended for real-time systems and especially for autonomous systems, like robots, UAVs, etc. The KaRL engine and transport system is powerful, but it is really only intended to maintain state in a thread-safe, distributed way. For robotics systems, you still need to access native drivers, programs, and code that makes the agent do something interesting to move within or modify its environment. MADARA provides this feature via attaching user callbacks to MADARA function calls. In this manner, predicates can be formed to reason about global or local state and react or interact accordingly.


More Information

The Java module is fully documented with standard java documentation. Please use the API at JavaDocs or tell your editor of choice to load source files from the Git repository.


Java Guide Series
Architecture | Knowledge Base | Networking | Containers | Threads