RoboComp is an open-source Robotics framework providing the tools to create and modify software components that communicate through public interfaces. Components may require, subscribe, implement or publish interfaces in a seamless way. Building new components is done using two domain specific languages, IDSL and CDSL. With IDSL you define an interface and with CDSL you specify how the component will communicate with the world. With this information, a code generator creates C++ and/or Python sources, based on CMake, that compile and execute flawlessly. When some of these features have to be changed, the component can be easily regenerated and all the user specific code is preserved thanks to a simple inheritance mechanism.

final week updates

Final Evaluation Updates

In this post I am concluding the work done during my GSOC period. I will outline the basic work done, results achieved and how to use the tools I have developed. Also I will provide a demo of the work done.

Work Completed

I have developed and thoroughly tested RCMaster. Also I have changed the C++ and python components to use RCMaster. Also these changes are incorporated into the code generator so that even the future components will use RCMaster by default.

I will list down the features of RCMaster now: * All the components can register to RCMaster in which they will be given a free port * The queries can be filtered by various parameters. e.g: A component can get the list of all the interfaces of name “test” running on the host 192.168.0.21 * RCMaster periodically saves the database so that even if the RCMaster crashes it can recover the data on restart and prevent the hassle of restarting the components. * RCMaster has a cache implemented so that if a component is crashed it will be assigned the same port on restart. So that all the previous connected components can continue the connection. * The components will now wait until the required interfaces are up and if any of the interfaces go down then they will suspend until they are up. This allows us to start the components in any order. * RCMaster is designed to produce meaningful exceptions.

Usage

Here I will describe how to generate a sample component and how to run it

First write the cdsl description of the component client1.cdsl

import "/robocomp/interfaces/IDSLs/RCMaster.idsl";
import "/robocomp/interfaces/IDSLs/Test.idsl";
import "/robocomp/interfaces/IDSLs/ASR.idsl";

Component client1{
	Communications{
    	requires rcmaster;
    	implements ASR,test;
	};
	language Python;
};

Now generate the component by running robocompdsl client1.cdsl ./

Now generate another component client2.cdsl by running robocompdsl client2.cdsl ./

import "/robocomp/interfaces/IDSLs/RCMaster.idsl";
import "/robocomp/interfaces/IDSLs/Test.idsl";
import "/robocomp/interfaces/IDSLs/ASR.idsl";

Component client2{
	Communications{
    	requires rcmaster,test;
	};
	language Python;
}; Now edit the client2 to use the test interface implemented by client1. Open `src/client2.py` file from the client2's directory and change the `@@COMP NAME HERE@@` to client1.

Edit the client to src/specificworker.py in client 2 and add the below code in the compute function.

try:
    self.proxyData["test"]["proxy"].printmsg("hello from " + self.name)
 except Ice.SocketException
       self.waitForComp("test",True)

Now edit the printmsg() in src/specificworker to add the below line

print message

Now we can run all the components.

First start the RCMaster and then the components

src/rcmaster.py    This will start RCMaster.    Now start client2 , then you can see that the client 2 is waiting for client1
src/client2.py –Ice.Config=etc/config

Now start client1

    src/client1.py --Ice.Config=etc/config

Now you can see the client1 printing “Hello from Client2” periodically. Play around

Demo video

You can see the video of the above demo here

All Commits

Pullrequest1

Pullrequest2

Pullrequest3


Regards,

Yash Sanap

RoboCompDSL's Tests

Hi!

After develop and following the Software Development Life Cycle:

I have created an script which contains tests for RoboCompDSL. This script allows generate easy components automatically with test code and run them. Users just have to supervise and continue with next test if everything works.

This work and more information can be found here.

How does it work?

tests.sh has a main with 3 options:

1.- Tests for CPP components.

2.- Tests for PYTHON components.

3.- Exit. (You can exit by simply pressing Ctrl + C too)

If you select first or second option, tests.sh will generate and run your components in /tmp/ directory. Why /tmp/? Simply, this tests are useless if everything works so in /tmp will not bother and will be removed after reboot. if you need to work with them, you can find them there or re-launching the script.

Is it nice?

Of course, this script works with Yakuake, and uses it to offer a much more pleasant and familiar test environment. It will create and divide tabs for each test.

Information of interest

Hello, in this post I will explain in summary form some technologies used in RoboCompDSL.

Model Driven Engineering MDE

Model driven engineering (Model-driven engineering) is a generic methodology that is based on knowledge of a specific domain to allow creating generic models (meta-models) of a problem to solve. This allows for greater abstraction of the problem and its surroundings, making it easier to find an efficient and complete solution. In this way, you can create models that users unfamiliar with the problem environment, could work in the domain of interest or even facilitate communication between individuals of a team working in various specific domains within the same working environment.

It is very common to use domain-specific languages ​​(Domain-Specific Language) that can be defined as text, diagrams, etc. To deal with these models. Have tools that deal with these DSLs could detect errors in the use of language or provide a user interface to enable work with models a simpler, guided and robust way. MDE use in oriented components can be an even intuitive programming practice because it would be very easy to specify a model for deployment and communications components or specification of the interfaces they use.

Domain-specific languages

There are many possible solutions that can be given for the same problem, choose one or the other will always depend on the quality of the result and the cost to get it. The domain-specific languages ​​can reduce the cost of carrying out a project without affecting the result. A clear example would be given in many cases where we have component-oriented routine code that could be specified once and reused the rest of the time schedule. DSLs are widely used in many projects such as SQL or HTML5.

State Machine Code Generation in Python

Equality between State Machine Code Generation in C++ and in Python

  • The language used for definition the state machine is the same.
  • The file of parsing of file that has the definition of state machine is the same.
  • The framework for the code of state machine is the same.

Codification of the state machine in Python

Robocompcdsl used a templates files to generate code written in python. I modified the following:

  • genericworker.py
  • specificworker.py

  • genericworker.py

This file contains the definition of state machine, slot functions, definition of the signals for transitions between states and the connections between signals and funtions.

  • specificworker.py

This file containd the code that is executed when the state machine active a state.

Example

  • genericworker.py

Example genericworker.py:

#
# Copyright (C) 2016 by YOUR NAME HERE
#
#    This file is part of RoboComp
#
#    RoboComp is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    RoboComp is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with RoboComp.  If not, see <http://www.gnu.org/licenses/>.

import sys
from PySide import *

class GenericWorker(QtCore.QObject):

	kill = QtCore.Signal()
#Signals for State Machine
	test1totest1 = QtCore.Signal()
	test1totest2 = QtCore.Signal()
	test2totest3 = QtCore.Signal()
	test2totest5 = QtCore.Signal()
	test2totest6 = QtCore.Signal()
	test3totest3 = QtCore.Signal()
	test3totest4 = QtCore.Signal()
	test4totest5 = QtCore.Signal()
	test5totest6 = QtCore.Signal()
	test1sub1totest1sub1 = QtCore.Signal()
	test1sub2totest1sub2 = QtCore.Signal()
	test1sub21totest1sub21 = QtCore.Signal()
	test1sub21totest1sub22 = QtCore.Signal()
	test3sub1totest3sub1 = QtCore.Signal()
	test3sub2totest3sub2 = QtCore.Signal()
	test5sub1totest1sub2 = QtCore.Signal()
	test1sub2totest5sub1 = QtCore.Signal()

#-------------------------

	def __init__(self, mprx):
		super(GenericWorker, self).__init__()


		self.jointmotor_proxy = mprx["JointMotorProxy"]

#State Machine
		self.Machine_testcpp= QtCore.QStateMachine()
		self.test2 = QtCore.QState(self.Machine_testcpp)
		self.test4 = QtCore.QState(self.Machine_testcpp)
		self.test5 = QtCore.QState(self.Machine_testcpp)

		self.test6 = QtCore.QFinalState(self.Machine_testcpp)

		self.test3 = QtCore.QState(QtCore.QState.ParallelStates, self.Machine_testcpp)
		self.test1 = QtCore.QState(QtCore.QState.ParallelStates,self.Machine_testcpp)


		self.test1sub1 = QtCore.QState(self.test1)
		self.test1sub2 = QtCore.QState(self.test1)




		self.test1sub21 = QtCore.QState(self.test1sub2)

		self.test1sub22 = QtCore.QFinalState(self.test1sub2)



		self.test3sub1 = QtCore.QState(self.test3)
		self.test3sub2 = QtCore.QState(self.test3)
		self.test3sub3 = QtCore.QState(self.test3)




		self.test5sub1 = QtCore.QState(self.test5)
		self.test5sub2 = QtCore.QState(self.test5)



#------------------
#Initialization State machine
		self.test1.addTransition(self.test1totest1, self.test1)
		self.test1.addTransition(self.test1totest2, self.test2)
		self.test2.addTransition(self.test2totest3, self.test3)
		self.test2.addTransition(self.test2totest5, self.test5)
		self.test2.addTransition(self.test2totest6, self.test6)
		self.test3.addTransition(self.test3totest3, self.test3)
		self.test3.addTransition(self.test3totest4, self.test4)
		self.test4.addTransition(self.test4totest5, self.test5)
		self.test5.addTransition(self.test5totest6, self.test6)
		self.test1sub1.addTransition(self.test1sub1totest1sub1, self.test1sub1)
		self.test1sub2.addTransition(self.test1sub2totest1sub2, self.test1sub2)
		self.test1sub21.addTransition(self.test1sub21totest1sub21, self.test1sub21)
		self.test1sub21.addTransition(self.test1sub21totest1sub22, self.test1sub22)
		self.test3sub1.addTransition(self.test3sub1totest3sub1, self.test3sub1)
		self.test3sub2.addTransition(self.test3sub2totest3sub2, self.test3sub2)
		self.test5sub1.addTransition(self.test5sub1totest1sub2, self.test1sub2)
		self.test1sub2.addTransition(self.test1sub2totest5sub1, self.test5sub1)


		self.test2.entered.connect(self.fun_test2)
		self.test3.entered.connect(self.fun_test3)
		self.test4.entered.connect(self.fun_test4)
		self.test5.entered.connect(self.fun_test5)
		self.test1.entered.connect(self.fun_test1)
		self.test6.entered.connect(self.fun_test6)
		self.test1sub1.entered.connect(self.fun_test1sub1)
		self.test1sub2.entered.connect(self.fun_test1sub2)
		self.test1sub21.entered.connect(self.fun_test1sub21)
		self.test1sub22.entered.connect(self.fun_test1sub22)
		self.test3sub1.entered.connect(self.fun_test3sub1)
		self.test3sub2.entered.connect(self.fun_test3sub2)
		self.test3sub3.entered.connect(self.fun_test3sub3)
		self.test5sub1.entered.connect(self.fun_test5sub1)
		self.test5sub2.entered.connect(self.fun_test5sub2)

		self.Machine_testcpp.setInitialState(self.test1)
		self.test1sub2.setInitialState(self.test1sub21)

#------------------
	
		self.mutex = QtCore.QMutex(QtCore.QMutex.Recursive)
		self.Period = 30
		self.timer = QtCore.QTimer(self)

#Slots funtion State Machine
	@QtCore.Slot()
	def fun_test2(self):
		print "Error: lack fun_test2 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test3(self):
		print "Error: lack fun_test3 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test4(self):
		print "Error: lack fun_test4 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test5(self):
		print "Error: lack fun_test5 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test1(self):
		print "Error: lack fun_test1 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test6(self):
		print "Error: lack fun_test6 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test1sub1(self):
		print "Error: lack fun_test1sub1 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test1sub2(self):
		print "Error: lack fun_test1sub2 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test1sub21(self):
		print "Error: lack fun_test1sub21 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test1sub22(self):
		print "Error: lack fun_test1sub22 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test3sub1(self):
		print "Error: lack fun_test3sub1 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test3sub2(self):
		print "Error: lack fun_test3sub2 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test3sub3(self):
		print "Error: lack fun_test3sub3 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test5sub1(self):
		print "Error: lack fun_test5sub1 in Specificworker"
		sys.exit(-1)

	@QtCore.Slot()
	def fun_test5sub2(self):
		print "Error: lack fun_test5sub2 in Specificworker"
		sys.exit(-1)


#-------------------------
	@QtCore.Slot()
	def killYourSelf(self):
		rDebug("Killing myself")
		self.kill.emit()

	# \brief Change compute period
	# @param per Period in ms
	@QtCore.Slot(int)
	def setPeriod(self, p):
		print "Period changed", p
		Period = p
		timer.start(Period)
  • specificworker.py

Example specificworker.py:

#
# Copyright (C) 2016 by YOUR NAME HERE
#
#    This file is part of RoboComp
#
#    RoboComp is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    RoboComp is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with RoboComp.  If not, see <http://www.gnu.org/licenses/>.
#

import sys, os, Ice, traceback, time

from PySide import *
from genericworker import *

ROBOCOMP = ''
try:
	ROBOCOMP = os.environ['ROBOCOMP']
except:
	print '$ROBOCOMP environment variable not set, using the default value /opt/robocomp'
	ROBOCOMP = '/opt/robocomp'
if len(ROBOCOMP)<1:
	print 'genericworker.py: ROBOCOMP environment variable not set! Exiting.'
	sys.exit()


preStr = "-I"+ROBOCOMP+"/interfaces/ --all "+ROBOCOMP+"/interfaces/"
Ice.loadSlice(preStr+"JointMotor.ice")
from RoboCompJointMotor import *



class SpecificWorker(GenericWorker):
	def __init__(self, proxy_map):
		super(SpecificWorker, self).__init__(proxy_map)
		self.Period = 2000
		self.timer.start(self.Period)
		self.Machine_testcpp.start()

	def setParams(self, params):
		#try:
		#	par = params["InnerModelPath"]
		#	innermodel_path=par.value
		#	innermodel = InnerModel(innermodel_path)
		#except:
		#	traceback.print_exc()
		#	print "Error reading config params"
		return True
#Slots funtion State Machine
	#
	# fun_test2
	#
	@QtCore.Slot()
	def fun_test2(self):
		pass

	#
	# fun_test3
	#
	@QtCore.Slot()
	def fun_test3(self):
		pass

	#
	# fun_test4
	#
	@QtCore.Slot()
	def fun_test4(self):
		pass

	#
	# fun_test5
	#
	@QtCore.Slot()
	def fun_test5(self):
		pass

	#
	# fun_test1
	#
	@QtCore.Slot()
	def fun_test1(self):
		pass

	#
	# fun_test6	
	#
	@QtCore.Slot()
	def fun_test6(self)
		pass

	#
	# fun_test1sub1
	#
	@QtCore.Slot()
	def fun_test1sub1(self):
		pass

	#
	# fun_test1sub2
	#
	@QtCore.Slot()
	def fun_test1sub2(self):
		pass

	#
	# fun_test1sub21
	#
	@QtCore.Slot()
	def fun_test1sub21(self):
		pass

	#
	# fun_test1sub22
	#
	@QtCore.Slot()
	def fun_test1sub22(self):
		pass

	#
	# fun_test3sub1
	#
	@QtCore.Slot()
	def fun_test3sub1(self):
		pass

	#
	# fun_test3sub2
	#
	@QtCore.Slot()
	def fun_test3sub2(self):
		pass

	#
	# fun_test3sub3
	#
	@QtCore.Slot()
	def fun_test3sub3(self):
		pass

	#
	# fun_test5sub1
	#
	@QtCore.Slot()
	def fun_test5sub1(self):
		pass

	#
	# fun_test5sub2
	#
	@QtCore.Slot()
	def fun_test5sub2(self):
		pass

Emit Signal

For realice a transition between states we has emit the corresponding signal. The signal is emit as follows:

self.statesrctostatedst.emit

Cog and PyParsing

Let’s go to know how RoboCompDSL works.

PyParsing

One of the most important modules used in RoboCompDSL is pyparsing. It is a Python module whose latest version to date is 2.0.4, which lets you create grammars in a simple way. Thanks to this, we can create Parsers that are in charge of a grammatical analysis of files that follow a grammatical structure. This module is vital in generating the code as it is used to interpret the DSLs defined in RoboComp.

A simple example of it would be:

from pyparsing import Word, alphas
greet = Word( alphas ) + "," + Word( alphas ) + "!"
hello = "Hello, World!"
print (hello, "->", greet.parseString( hello ))

Output:

$ Hello, World! -> ['Hello', ',', 'World', '!']

This module is used in scripts parseIDSL.py and parseCDSL.py to extract the files CDSL and IDSL necessary information that will be used to generate code routine.

Cog

Cog is a code generator written in Python that allows you to inject Python code within a file so that, after using the generator, we get a result that depends on the injected code. It can be used with any type of file so it does not limit its use to code generation. The RoboCompDSL tool uses this code generator for creating and modifying components of a quick and easy basis of information obtained from CDSL and IDSL files. This tool is able to generate code routine that hindered the creation of complex systems components.

An example of use is shown in a file either:

Static part of our file
[[[cog
import cog
fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
for fn in fnames:
    cog.outl("void %s();" % fn)
]]]
[[[end]]]
Another fragment of our file

Running Cog:

static part of the file
void DoSomething();
void DoAnotherThing();
void DoLastThing();
Another fragment of our file