Tutorial Calculator

From MARIEWiki

Jump to: navigation, search

Contents

Description

The goal of this tutorial is to learn you how to create your own project with the element of base of MARIE. The goal of this project is to create a calculator: we want to send an operation in its input and we want the result in its output. To do that you will learn to create many elements :

  • A new data : DataOperation which will contain the elements of your operation
  • A new component : aaCalulator which will make the operation
  • And finally, how to create and run your project with your new component

This tutorial is for the beginner and doesn't use all the possibility give by MARIE.

Required softwares

  • MARIE 0.5

Please refer to MARIE 0.5 Installation Guide for installation instructions.


Walkthrough

The Big Picture

aaCalculator.jpg

Create a custom datatype

First, you need to create your own type of data : DataOperation. This data will include all the elements need for a basic operation :

  • Two operands
  • An operator (+,-,* or /)
  • The result of the operation

Create your files

  • To get started to create your own data, you could fin empty shell here:

This files is constitute by one class (MyData) which must describe all the elements and the functions need to handle the data

  • In each source files search and replace the following :
    • MyData by DataOperation

Make your variables members

  • In the template, a variable is already create: static const std::string ID:. It contains the name of your new data to identify it. So, you must give a name to your data : DATAOPERATION :
const std::string DataOperation::ID = "DATAOPERATION";
  • Then, you could create all the variables need for your operation (the operands, the operator and the result):
 protected : 
    double m_operand1;
    double m_operand2;
    double m_result;
    std::string m_operatorType;

Make your members functions

  • You could create many constructor.
    • One initialize your object with operands, operator and result
    • DataOperation::DataOperation(double operand1, double operand2, double result,std::string operatorType) : DataAbstract(ID),
      m_operand1(operand1),
      m_operand2(operand2),
      m_result(result),
      m_operatorType(operatorType)
      {   
      }
      
    • One make the copy of an other one :
    • DataOperation::DataOperation(const DataOperation &cpy) : DataAbstract(cpy)
      {
           m_operand1 = cpy.getOperand1();
           m_operand2 = cpy.getOperand2();
           m_result = cpy.getResult();
           m_operatorType = cpy.getOperatorType();
      }.
      
  • Then, you must create the functions useful to use your variable:
  • void DataOperation::setOperand1(double operand1)
    {
       m_operand1 = operand1;
    }  
    void DataOperation::setOperand2(double operand2)
    {
       m_operand2 = operand2;
    }  
    void DataOperation::setResult(double result)
    {
       m_result = result;
    }
    void DataOperation::setOperatorType(std::string operatorType)
    {
       m_operatorType = operatorType;
    }   
    double DataOperation::getOperand1() const
    {
       return(m_operand1);
    }   
    double DataOperation::getOperand2() const
    {
       return(m_operand2);
    }  
    double DataOperation::getResult() const
    {
       return(m_result);
    }   
    std::string DataOperation::getOperatorType() const
    {
       return(m_operatorType);
    }
    bool DataOperation::operator==(const marie::DataAbstract& operand) const
    {
         if(DataAbstract::operator==(operand))
         {
            const DataOperation& castedOperand = dynamic_cast<const DataOperation&>(operand);
            return((castedOperand.m_operand1 == m_operand1) && (castedOperand.m_operand2 == m_operand2) &&
               (castedOperand.m_operatorType == m_operatorType) &&
               (castedOperand.m_result == m_result));
         }
         else
         {
            return(false);
         }
    }
    

Create SerDes for the custom datatype

  • You could convert your data in different protocol. Here, we will see 2 protocol used by MARIE : XML and Pretty.

Create the DataOperation files

  • To get started to create your own Serdes, you could fin empty shell here:

TODO : upload the templates

For each protocol, we have on class : SerDesMarieXMLDataOperation and SerDesPrettyDataOperation

  • In each source files search and replace the following :
    • MyData by DataOperation

Build the XML tree

  • You could see in the template the function buildXMLTree. This function read the variables of DataOperation and convert into XML. You could find an example for one variable, you must do the same thing for all the variables of DataOperation:
    xmlNodePtr myDataNode;
    xmlNodePtr VariableNode;
    myDataNode = xmlNewTextChild(parent, NULL, (xmlChar *)"MYDATA", (xmlChar *));      
    std::stringstream tmp;
    tmp << data.getVariable();
    operand1Node = xmlNewTextChild(myDataNode, NULL, (xmlChar *)"VARIABLE", (xmlChar *)tmp.str().c_str());
    tmp.str("");
    tmp.clear();;      

The name you give here (MYDATA) must be the same as the one give to the DataOperation ID (DATAOPERATION)
The name you give to your variables (VARIABLE) will be uesful after when you will run your project

Build the data

  • You could see in the template the function buildMyData. This function read the XML format and convert into DataOperation. You could find an example for one variable, you must do the same thing for all the variables of DataOperation:
     double variable; 
     transferStr.str(getElementContent(xpathCtx, (const xmlChar*)((prefix + "MYDATA/VARIABLE").c_str())));
     if(transferStr.str() == "")
        {return(NULL);}
     transferStr >> variable;      
     dataResult->setVariable(variable);

Use the same name of variables that used above.

Make the data in Pretty

  • You could see in the template the function toPretty. This function read the variables of DataOperation and print the data. You could find an example for one variable, you must do the same thing for all the variables of DataOperation:
      sstr << "Variable : " << castedData.getVariable() << std::end

Test

  • To check if the new SerDes have been loaded properly, you can use the marie-info program.

Create the Calculator component

Now, you will create your own component called aaCalculator. The goal of this component is to make a basic operation. It possess 1 input and 1 output. The data type used is DataOperation. You transmit in input your operation and you can find the solving in output.

Create the aaCalculator Files

  • To make your component, you need to create many file :
  1. aaCalculator.cpp : The main of the component. It create and active the component.
  2. CalculatorVisitorConfig.cpp / CalculatorVisitorConfig.h : Configure the component (port, logger)
  3. CalculatorComponentHandler.cpp / CalculatorComponentHandler.h : The core of the component. It receive, handle and send the data.
  • To get started to create your own component, you could fin empty shell and explication to adapt them to your component here : ComponentHandlerQueue
  • In each source files, search and replace the following :
    • YourComponentName by Calculator;
    • YOUR_COMPONENT_NAME by CALCULATOR;
    • MarieDataType by DataOperation;
    • m_marieDataType by m_dataOperation;
    • marieDataType by dataOperation;
    • InputName by OperationInput;
    • inputName by operationInput;
    • m_inputName by m_operationInput;
    • m_outputNamePort by m_resultOutput;
    • outputname by resultOutput

Create the configuration of aaCalculator

The class CalculatorVisitorConfig create the configuration of your component. aaCalculator is an elementary component so it doesn't need special configuration. You could check that you have :

  • 1 configuration : Calculator. In this section of your configuration file, you will put all the configuration elements need by your component.
  • 2 qualifiers different : inputs and outputs. In this section of your configuration file, you could create your input and output of your component.
  • 1 type : ports. In this section of your configuration file, you could create your ports. The name of your ports must be the same that the ones use at the initialization of the component.

The aaCalculator configuration could be summarize like that : (see Configuration Description Language for syntax details and projet file for the XML version)

# C Calculator    
  # Q inputs
    # T port
      # KV type = Default
      # KV name = operationInput
      # KV connectionName
      # T cs
         # KV type = SocketAcceptor
         o KV name
         # KV portnumber
         o KV hostname
      # Q  receiveCFBs
         # T cfb
           # KV type = MarieXMLExtractor
           # KV convertToDataAbstract = true
         o T cfb
           # KV type = QtProbe
           o KV name
           o KV position
           o KV size
           o KV protocolID = MARIEPRETTY

  # Q outputs
    # T port
      # KV type = Default
      # KV name = resultOutput
      # KV connectionName
      # T cs
         # KV type = SocketAcceptor
         o KV name
         # KV portnumber
         o KV hostname
      # Q  sendCFBs
         # T cfb
           # KV type = MarieXMLFormatter           
         o T cfb
           # KV type = QtProbe
           o KV name
           o KV position
           o KV size
           o KV protocolID = MARIEPRETTY
  • In this configuration, we use SocketAccepor. With this port, we could choice manually the port number. It will be helpful when you would send an operation manually in your component. So remember the portnumber choice.

Initialization

This section is already made in the template. You must just see that in the function init(), all the ports are initialize :

In input :

while(!iterInputs.done())
        {
           PortAbstract* port = iterInputs.next();
           
           if(port-> getName() == "operationInput")
           {
              m_operationInputPort = port;
              m_owner->registerPort(*port);               
              CommBroker::getInstance().registerCommReceiverListener(port->getName(), *m_operationinputCommHandler);
           }
           
           iterInputs++;
        }
        

and output:

 while(!iterOutputs.done())
        {
           PortAbstract* port = iterOutputs.next();
           
           if(port->getName() == "resultOutput")
           {
              m_resultOutputPort = port;
              m_owner->registerPort(*port);
           }
           iterOutputs++;
        }

the name you give to your ports (red) must be the same that your configuration file

For example, if you have more of one port, the syntax could be like :

while(!iterInputs.done())
        {
           PortAbstract* port = iterInputs.next();
           
           if(port-> getName() == "input1")
           {
              m_Input1Port = port;
              m_owner->registerPort(*port);               
              CommBroker::getInstance().registerCommReceiverListener(port->getName(), *m_operationinputCommHandler);
           }
           elsif(port-> getName() == "Input2")
           {
              m_Input2Port = port;
              m_owner->registerPort(*port);               
              CommBroker::getInstance().registerCommReceiverListener(port->getName(), *m_operationinputCommHandler);
           }
            elsif(port-> getName() == "Input3")
           {
              m_Input3Port = port;
              m_owner->registerPort(*port);
              CommBroker::getInstance().registerCommReceiverListener(port->getName(), *m_operationinputCommHandler);
           }
                    .....
                    .....
                    .....
                    .....
           iterInputs++;
        }

Reception of the data

the class OperationInputCommHandler of the class CalculatorComponentHandler manage the reception of the data of the input port. In your case, the component will only receive DataAbstract data so the only function use is : recvData(const DataAbstract& data). This function must :

  • Check if the data receive is a DATAOPERATION : return false if no.
  • Convert the DataAbstract receive by a DataOperation (dynamic_cast).
  • Make a request to handle the data : create an object OperationInputRequest.
  • Put this request in the request queue. The answer of this request will be the function call() of OperationInputRequest.
  bool CalculatorComponentHandler::OperationInputCommHandler::recvData(const DataAbstract& data)
  {
     ACE_Guard<ACE_Thread_Mutex> guard(m_owner->m_mutex);
    // if started, build a request to process the incoming input    
     if(m_owner->m_startFlag)
     {	      
       // Verify datatype
        if((data.getID() == "DATAOPERATION"))
        {
           const DataOperation *OperationInputData = dynamic_cast<const DataOperation*>(&data); 
           OperationInputRequest *request = new OperationInputRequest(*m_owner,new DataOperation(*OperationInputData)); 
           m_owner->m_requestQueue->enqueue(*request); 
           return(true); 
        }
        else
        {
           return(false);
        }
     }
     return(false);
  }

Response of an input request

the class OperationInputRequest of the class CalculatorComponentHandler manage the response of an input request. When there is a request in the input request queue the fuction call() of this object is call :

  int CalculatorComponentHandler::OperationInputRequest::call()
  {
     m_handler->handleOperationInputData(*m_dataOperation);
     return(0);
  }

Response of an output request

In the template, this section isn't make. So you must :

  • Make a new class OperationOutputRequest in the class CalculatorComponentHandler like OperationInputRequest which manage the response of an output request.
  • The function call() must execute the process associate to the output request: handleOperationOutputData() to handle the data.
  int CalculatorComponentHandler::OperationOutputRequest::call()
  {
     m_handler->handleOperationOutputData(*m_dataOperation);
     return(0);
  }

Handle of the data receive

The handle of the data receive is made by the function handleOperationInputData(). In this function, you must :

  • Make the calculation of the operation with a dataOperation. To do this you could make a new function : Calculate(DataOperation& dataOperation)
  • Make a request to send the data result: create an object OperationOutputRequest
  • Put this request in the request queue. The answer of this request will be the function call() of OperationOutputRequest.
   void CalculatorComponentHandler::handleOperationInputData(DataOperation& dataOperation)
   {
    // It is possible to use the communication thread to send data here.
    MARIE_LOG_INFO_TRY("AACalculator.RequestHandler.Input.calculate","Calculate the operation");
    if(Calculate(dataOperation)) MARIE_LOG_INFO_SUCCEEDED("marie.RequestHandler.Input.calculate");
    else MARIE_LOG_INFO_FAILED("AACalculator.RequestHandler.Input.calculate");
    OperationOutputRequest *request = new OperationOutputRequest(*this,new DataOperation(dataOperation));
    m_requestQueue->enqueue(*request); 
   }

Calculation of the operation

Now, we want create a new function which will perform the calculation of our operation. To do that, you must:

  • Create a new function : Calculate(DataOperation& dataOperation)
  • Make an algorithm which test the operator and make the operation associate
  • Here, you could put some log see TODO lien
  bool CalculatorComponentHandler::Calculate(DataOperation& dataOperation)
  {
       if(dataOperation.getOperatorType()=="+") dataOperation.setResult( dataOperation.getOperand1() + dataOperation.getOperand2() );
       else if(dataOperation.getOperatorType()=="-") dataOperation.setResult( dataOperation.getOperand1() - dataOperation.getOperand2() );
       else if(dataOperation.getOperatorType()=="*") dataOperation.setResult( dataOperation.getOperand1() * dataOperation.getOperand2() );
       else if(dataOperation.getOperatorType()=="/") 
       {
             if(dataOperation.getOperand2()!=0) dataOperation.setResult( dataOperation.getOperand1() / dataOperation.getOperand2() );
             else 
             {
                   dataOperation.setResult(0);
                   MARIE_LOG_WARNING("AACalculator.RequestHandler.Input.calculate","Result set to 0, division by 0 impossible");
                   return false;
             }
       }
       else 
       {
             dataOperation.setResult(0);
             MARIE_LOG_WARNING("AACalculator.RequestHandler.Input.calculate","Result set to 0, operator of type [" + dataOperation.getOperatorType() + "] -> Not Supported");
             return false;
       }
       dataOperation.setOperand1(dataOperation.getResult());
       return true;	
  }


Handle of the data result

The handle of the data result is made by the function handleOperationOutputData(DataOperation& dataOperation). In this function, you must :

  • Send the data result in the output port
  void CalculatorComponentHandler::handleOperationOutputData(DataOperation& dataOperation)
  {
     m_resultOutputPort->sendData(dataOperation);      
  }

Create the Calculator project

Now, you have create all the elements need to construct your project. This project is elementary : you are only one component to insert. You will send data manually in the input in XML. You could see your data transmit and the data result in 2 windows separate (QtProbe). The display could be in pretty instead of XML for better readability.

Create the project files

  • AACalculator Configuration file
    • To configure your component, you need to create a XML file like calculatorConfig.xml. This file must correspond to the configuration make here : Configuration of aaCalculator. The XML files could be like :
<Calculator elem="conf">
 <inputs elem="q">
    <port elem="type">
       <type elem="kv">Default</type>
       <name elem="kv">operationInput</name>
       <connectionName elem="kv">input</connectionName>
       <cs elem="type">
          <type elem="kv">SocketAcceptor</type>
          <portnumber elem="kv">12000</portnumber>
          <hostname elem="kv">hostname</hostname>
       </cs>
       <receiveCFBs elem="q">
          <cfb elem="type">
             <type elem="kv">MarieXMLExtractor</type>
             <convertToDataAbstract elem="kv">true</convertToDataAbstract>
          </cfb>
          <cfb elem="type">
             <type elem="kv">QtProbe</type>
             <name elem="kv">Input</name>
             <position elem="kv">1 1</position>
             <size elem="kv">410 300</size>
             <protocolID elem="kv">MARIEXML</protocolID>
          </cfb>
       </receiveCFBs>
       <datatype elem="kv">any</datatype>
       <description elem="kv"></description>
    </port> 
 </inputs>
 <outputs elem="q">     
    <port elem="type">
       <type elem="kv">Default</type>
       <name elem="kv">resultOutput</name>
       <connectionName elem="kv">output</connectionName>
       <cs elem="type">
          <type elem="kv">SocketAcceptor</type>
          <portnumber elem="kv">12001</portnumber>
          <hostname elem="kv">hostname</hostname>
       </cs>
       <sendCFBs elem="q">
          <cfb elem="type">
             <type elem="kv">QtProbe</type>
             <name elem="kv">Output</name>
             <position elem="kv">430 1</position>
             <size elem="kv">410 300</size>
             <protocolID elem="kv">MARIEXML</protocolID>
          </cfb>
          <cfb elem="type">
             <type elem="kv">MarieXMLFormatter</type>
          </cfb>
       </sendCFBs>
       <datatype elem="kv">any</datatype>
       <description elem="kv"></description>
    </port>
  </outputs>  
</Calculator>

You could note that we choice :

  • 12000 for the input port
  • 12001 for the output port
  • Setup file
    • To configure your project, you need to create a setup file like calc.setup. ( see setup configuration). In our case, we have only one process (aaCalculator) so the XML files could be like :
<?xml version="1.0"?>

<setup elem="conf">
  <version elem="type">
     <name elem="kv">Calculator - Example/name>
  </version>
  <node elem="type">
        <name elem="kv">hostname</name>         
        <process elem="type">
           <name elem="kv">Calculator</name>
           <type elem="kv">MARIE:MPI</type>
           <executableName elem="kv">aaCalculator</executableName>
           <configuratorFile elem="kv">./CalculatorConfig.xml</configuratorFile>
        </process>
  </node>
</setup>
  • Project file
    • Finally, you need to create a project file like calculator.prj. ( see project configuration). We have only one setup (calc.setup) so the XML files could be like :
<?xml version="1.0"?>

<project elem="conf">
  <version elem="type">
     <name elem="kv">Calculator - Example</name>
  </version>
  <setup elem="type">
     <name elem ="kv">Calc</name>
     <fileLocation elem="kv">./Calc.setup</fileLocation>
</project>
  • DataOperation file
    • You need to create a DataOperation file: operation.xml which contain your operation in XML. It look like :
<?xml version="1.0"?>

<MARIE>    
  <DATAOPERATION>
      <OPERAND1>123</OPERAND1>
       <OPERAND2>456</OPERAND2>           
       <RESULT>0</RESULT>
       <OPERATORTYPE>+</OPERATORTYPE>           
  </DATAOPERATION> 
  <TIMESTAMP>
       <SEC>1173844251</SEC>
       <USEC>234609</USEC>
       </TIMESTAMP>
</MARIE>

The syntax must respect the one use in the class SerDesMarieXMLDataOperation.

Start the project with apman

In this way, the launch of the project is less automatic but for a first approach you could more understand the different steps. You don't need to use the files : calculator.prj and calc.setup.

1) Launch the component

  • You could use this command :
aaCalculator -n Calculator -d 12010 -c 12011 

aaCalculator : executable of the component
-n : name of the component. If you used the same component Several times, you must give a different name (Calculator1, Calculator2, ...)
-d : director port
-c : configurator port

Now, your component is ready to communicate. The next steps must be done in a new terminal.

2) Configure the component

  • You must send your configuration file in the director port of your component :
appman -p 12011 -c sxml:CalculatorConfig.xml

-p : configurator port
-c sxml : name of your configuration file

3) Initialize the component

  • You must initialize your component by send an initialization signal in the director port of the component :
appman -p 12010 -c init

-p : director port
-c init : initialization signal

4) Activate the ports

  • You must activate the ports of component input and output by send a signal of activation of ports in the director port of the component :
appman -p 12010 -c activateAllPorts

-p : director port
-c activateAllPorts : activate all ports signal

5) Start the component

  • You must start your component by send a start signal in the director port of the component :
appman -p 12010 -c start

-p : director port
-c start : start signal

  • Now, your component is ready to work! You could automatize this step by creating a shell script like :
#!/usr/bin/env csh
 
appman -p 12013 -c sxml:CalculatorConfig.xml
sleep 2
appman -p 12012 -c init
sleep 2
appman -p 12012 -c activateAllPorts
sleep 2
appman -p 12012 -c start

Start the project With ammpi

It's a way more automatic and more easy to launch a project. In this way, you use your setup and project files with this command :

ammpi calculator.prj Calc.setup

Send an operation

  • Now, your component is start and ready to work. You could send an operation in its director port :
appman -h HostName -p 12000 -c sdata:./operation.xml

-h : HostName
-p : port number choice for the input (see AACalculator Configuration file)
-c sdata : your operation file

  • If all work, you could see your operation send in the QtProbe of the input and the operation result in the QtProbe of the Output. Well done, you finish to create your first own component!!

Personal tools