Configuring and Starting Components from an Orocos Script

Purpose

To start an Orocos application without writing a single XML file. Note: this syntax is only possible from RTT 2.2.0 on. pre-2.2.0 versions only support scripts in 'program' blocks.

You'll need to have the Scripting Chapter of the Component Builder's Manual at hand for clarifications on syntax and execution semantics.

How it works

We write one or more scripts that locate the components on the filesystem, create them in the application and connect and configure them. We use the DeploymentComponent's scripting API to do all this, instead of using a DeploymentComponent XML file.

How it's done

Create a new file 'startup.ops'. 'ops' stands for Orocos Program Script. And write this code:

path("/opt/orocos/lib/orocos") // Path to where components are located   [1]
import("myproject")            // imports a specific project in the path [2]
import("ocl")                  // imports ocl from the path
require("print")               // loads the 'print' service globally.    [3]
 
loadComponent("HMI1","OCL::HMIComponent") // create a new HMI component [4]
loadComponent("Controller1","MyProjectController") // create a new controller
loadComponent("Test1","TaskContext")      // creates an empty test component

You can test this code by doing:

deployer-gnulinux -s startup.ops
OR, at the taskbrowser prompt:
deployer-gnulinux
...
Deployer [S]> help runScript 
 
 runScript( string const& File ) : bool
   Runs a script.
   File : An Orocos program script.
Deployer[S]> runScript("startup.ops")

The first line of startup.ops ([1]) extends the standard search path for components. Every component library directly in a path will be discovered using this statement, but the paths are not recursively searched. For loading components in subdirectories of a path directory, use the import statement. In our example, it will look for the myproject directory in the component paths and the ocl directory. All libraries and plugins in these directories will be loaded as well.

After importing, we can create components using loadComponent ([4]). The first argument is the name of the component instance, the second argument is the class type of the component. When these lines are executed, 3 new components have been created: HMI1, Controller1 and Test1.

Finally, the line require("print") loads the printing service globally such that your script can use the 'print.ln("text")' function. See help print in the TaskBrowser after you typed require("print").

Now extend the script to include the lines below. The create connection policy objects and connect ports between components.

// See the Doxygen API documentation of RTT for the fields of this struct:
var ConnPolicy cp_1
// set the fields of cp_1 to an application-specific value:
cp_1.type = BUFFER  // Use ''BUFFER'' or ''DATA''
cp_1.size = 10      // size of the buffer
cp_1.lock_policy = LOCKED // Use  ''LOCKED'', ''LOCK_FREE'' or ''UNSYNC''
// other fields exist too...
 
// Start connecting ports:
connect("HMI1.positions","Controller1.positions", cp_1)
cp_1 = ConnPolicy() // reset to defaults (DATA, LOCK_FREE)
connect("HMI1.commands","Controller1.commands", cp_1)
// etc...

Connecting data ports is done using ConnPolicy structs that describe the properties of the connection to be formed. You may re-use the ConnPolicy variable, or create new ones for each connection you form. The Component Builder's Manual has more details on how the ConnPolicy struct influences how connections are configured.

Finally, we configure and start our components:

if ( HMI1.configure() == false )
   print.ln("HMI1 configuration failed!")
else {
   if ( Controller1.configure() == false )
      print.ln("Controller1 configuration failed!")
   else {
      HMI1.start()
      Controller1.start()
   }
}

Advanced configuration using a State Machine

For complexer scenarios, we can put our logic in a state machine that starts/stops/configures components as we go:
StateMachine SetupShutdown {
    var bool do_cleanup = false, could_config = false;
    initial state setup {
           entry {
                // Configure components
                could_config = HMI1.configure() && Controller1.configure();
                if (could_config) {
                    HMI1.start();
                    Controller1.start();
                }
           }
           transitions { 
                if do_cleanup then select shutdown;
                if could_config == false then select failure;
           }
    }
 
    state failure {
           entry {
                print.ln("Failed to configure a component!")
           }
    }
 
    final state shutdown {
           entry {
                // Cleanup B group
                HMI1.stop() ; Controller1.stop();
                HMI1.cleanup() ; Controller1.cleanup();
           }
    }
}
RootMachine SetupShutdown deployApp;
 
deployApp.activate()
deployApp.start()

State machines are explained in detail in the Scripting Chapter of the Component Builder's Manual.